- Rename http_framework/ws_server.lua → rgl_framework.lua and update all internal references (log path, script name, init message) - Replace debug eprintln!() in bridge.rs with logging::log() calls so bridge diagnostics go to log file and admin panel instead of stderr - Add DEBUG logs: bridge requests/polls, WS connect/disconnect, API timeout warnings, DB batch JSON parse errors - Move log file from /sdcard/Download/ to getWorkingDirectory()/logs/ with auto-creation of logs/ directory in logging::init() - Remove unused debug_pending_count() from bridge.rs - Add project documentation: CLAUDE.md, docs/MODULE_GUIDE.md, docs/LIB_REFERENCE.md, docs/TASKS.md Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>main
parent
9e2fdd4c5b
commit
e92bbdb62c
@ -0,0 +1,140 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
|
||||||
|
ARZ Web Helper is a modular web-based control panel for Arizona RP (SA-MP/MoonLoader game mod). It has two layers:
|
||||||
|
|
||||||
|
- **Rust core** (`rust_core/`) — an Axum HTTP/WebSocket server compiled as a shared library (`libarz_core.so`). Exposes a C FFI (`rgl_*` functions) for the Lua side. Handles HTTP routing, WebSocket event broadcasting, SQLite persistence, and static file serving.
|
||||||
|
- **Lua framework** (`http_framework/rgl_framework.lua`) — the main MoonLoader script (~1058 lines). Loads the Rust `.so` via LuaJIT FFI, sets up the framework API, loads modules, and runs the game-thread event loop.
|
||||||
|
|
||||||
|
## Build Commands
|
||||||
|
|
||||||
|
### Rust core (cross-compile for Android aarch64)
|
||||||
|
```bash
|
||||||
|
cd rust_core
|
||||||
|
cargo build --release --target aarch64-linux-android
|
||||||
|
```
|
||||||
|
The output `.so` is deployed to the game device at `lib/libarz_core.so` relative to the MoonLoader working directory.
|
||||||
|
|
||||||
|
### Rust tests
|
||||||
|
```bash
|
||||||
|
cd rust_core
|
||||||
|
cargo test
|
||||||
|
```
|
||||||
|
Tests exist in `rust_core/src/db.rs` (7 tests covering batch DB operations).
|
||||||
|
|
||||||
|
### Lua modules
|
||||||
|
No build step. Lua files are loaded at runtime by MoonLoader from the `modules/` directory on the device.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
### Rust ↔ Lua Bridge
|
||||||
|
The Rust server and Lua game thread communicate through a poll-based bridge (`rust_core/src/bridge.rs`):
|
||||||
|
- **Lua → Rust**: `rgl_push_event()` sends game events (SAMP network events) to connected WebSocket clients via broadcast channel (capacity 256).
|
||||||
|
- **Rust → Lua**: HTTP API requests become Lua code strings queued via `bridge::request_lua_exec()`. Lua's `main()` loop calls `rgl_poll()` each frame, executes the code, and returns results via `rgl_respond()`. API timeout is 2 seconds.
|
||||||
|
- All strings crossing the FFI boundary may be Win-1251 encoded (game uses CP1251). The Rust side converts to UTF-8 via `encoding_rs`.
|
||||||
|
|
||||||
|
### Module System
|
||||||
|
Modules live in `http_framework/modules/<name>/init.lua` (bundled with framework) or `more_modules/<name>/init.lua` (separate, gitignored). Both are loaded identically at runtime. Each module exports:
|
||||||
|
- `M.init(fw)` — required. Receives the framework API table (namespaced per module).
|
||||||
|
- `M.render(ui, state)` — optional. Provides a UI via the abstract widget builder (works both as web JSON and in-game imgui).
|
||||||
|
- `M.unload()` — optional cleanup.
|
||||||
|
|
||||||
|
The framework wraps each module's DB calls with a `<module_name>.` key prefix for isolation. Modules register API handlers via `fw.on_api(module, action, handler)` and game event interceptors via `fw.on_event(event_name, handler, priority)`.
|
||||||
|
|
||||||
|
See `docs/MODULE_GUIDE.md` for the full module authoring reference.
|
||||||
|
|
||||||
|
### Dual UI Rendering
|
||||||
|
Modules write UI using an abstract `ui` API (`ui.text()`, `ui.button()`, `ui.tab_bar()`, etc.). Two implementations exist:
|
||||||
|
- `create_ui_builder()` (rgl_framework.lua:129) — records widgets as JSON for the web frontend (`/m/<module>/` served via `ui_page.html`).
|
||||||
|
- `create_ui_imgui()` (rgl_framework.lua:267) — wraps `mimgui` (ImGui) for in-game overlay rendering.
|
||||||
|
|
||||||
|
Modules with `render()` but no `static/` directory get auto-registered as `__render__` modules, which are served the generic `ui_page.html` template.
|
||||||
|
|
||||||
|
### HTTP Routes (server.rs)
|
||||||
|
- `GET /` — dashboard listing all registered modules
|
||||||
|
- `GET /admin` — admin panel (module management, DB browser, logs)
|
||||||
|
- `GET /m/<module>/` — module UI (static files or auto-rendered via ui_page.html)
|
||||||
|
- `POST /api/<module>/<action>` — module API endpoint (routed to Lua via bridge)
|
||||||
|
- `GET /api/modules` — JSON list of loaded modules
|
||||||
|
- `GET /api/commands` — JSON list of registered commands
|
||||||
|
- `GET /ws` — WebSocket upgrade for real-time game events
|
||||||
|
|
||||||
|
### Framework API Quick Reference
|
||||||
|
|
||||||
|
Core `fw.*` methods available to modules:
|
||||||
|
|
||||||
|
| Method | Purpose |
|
||||||
|
|--------|---------|
|
||||||
|
| `fw.log(level, tag, ...)` | Log message (also broadcasts to WS as `__log`) |
|
||||||
|
| `fw.on_api(mod, action, fn)` | Register HTTP API handler |
|
||||||
|
| `fw.on_event(name, fn, priority)` | Hook SA-MP event (return false to block) |
|
||||||
|
| `fw.command(name, fn)` | Register `/name` chat command |
|
||||||
|
| `fw.db_get(key)` / `fw.db_set(key, val)` | Single key DB ops (auto-prefixed) |
|
||||||
|
| `fw.db_get_many(keys)` / `fw.db_set_many(kv)` | Batch DB ops |
|
||||||
|
| `fw.db_get_prefix(prefix)` | Get all keys with prefix |
|
||||||
|
| `fw.timer_once(ms, fn)` | One-shot timer, returns cancel function |
|
||||||
|
| `fw.chat(text)` | Send chat message/command |
|
||||||
|
| `fw.add_chat_message(text, color)` | Local-only chat message |
|
||||||
|
| `fw.respond_dialog(id, btn, list, input)` | Respond to server dialog |
|
||||||
|
| `fw.unlock_dialog()` | Send /mm + auto-close to unblock dialog |
|
||||||
|
| `fw.json_encode(t)` / `fw.json_decode(s)` | JSON serialization (cjson) |
|
||||||
|
| `fw.to_utf8(s)` / `fw.to_win1251(s)` | Encoding conversion |
|
||||||
|
| `fw.register_module(name, dir)` | Register static file serving |
|
||||||
|
| `fw.push_event(name, json)` | Broadcast custom event to WS |
|
||||||
|
|
||||||
|
### UI Widget Types
|
||||||
|
|
||||||
|
Available in `render(ui, state)`:
|
||||||
|
|
||||||
|
| Widget | Signature | Returns |
|
||||||
|
|--------|-----------|---------|
|
||||||
|
| `text` | `ui.text(str)` | — |
|
||||||
|
| `text_colored` | `ui.text_colored(r,g,b,a, str)` | — |
|
||||||
|
| `separator` | `ui.separator()` | — |
|
||||||
|
| `spacing` | `ui.spacing()` | — |
|
||||||
|
| `sameline` | `ui.sameline()` | — |
|
||||||
|
| `button` | `ui.button(label)` | `true` if clicked |
|
||||||
|
| `input` | `ui.input(label, value)` | new string value |
|
||||||
|
| `input_int` | `ui.input_int(label, value)` | new int value |
|
||||||
|
| `checkbox` | `ui.checkbox(label, bool)` | new bool |
|
||||||
|
| `slider_int` | `ui.slider_int(label, val, min, max)` | new int |
|
||||||
|
| `slider_float` | `ui.slider_float(label, val, min, max)` | new float |
|
||||||
|
| `combo` | `ui.combo(label, idx, items)` | new index (0-based) |
|
||||||
|
| `progress` | `ui.progress(fraction, label)` | — |
|
||||||
|
| `collapsing` | `ui.collapsing(label)` | true (call `collapsing_end()`) |
|
||||||
|
| `tab_bar` | `ui.tab_bar(id)` | true |
|
||||||
|
| `tab_item` | `ui.tab_item(label)` | true (call `tab_end()`) |
|
||||||
|
|
||||||
|
### Key Conventions
|
||||||
|
- Game chat commands: short names without prefix for modules (`/btc`, `/btcc`), `rgl_` prefix for core (`/rgl_admin`).
|
||||||
|
- Dialog IDs are hardcoded SA-MP server dialog identifiers (e.g., 7238, 25244). Handlers return `false` to hide dialog from player.
|
||||||
|
- Event interceptors sorted by priority (lower = earlier). Return `false` to block event propagation.
|
||||||
|
- DB operations support async batching via `rgl_db_submit()`/`rgl_db_poll()` for bulk reads/writes.
|
||||||
|
- Win-1251 Cyrillic matching in dialog text uses hex escapes (e.g. `\xc2\xea\xeb\xfe\xf7` for "Включ").
|
||||||
|
|
||||||
|
### Key Files
|
||||||
|
|
||||||
|
| File | Lines | Purpose |
|
||||||
|
|------|-------|---------|
|
||||||
|
| `http_framework/rgl_framework.lua` | ~1058 | Main framework: init, module loading, UI builders, event hooks, admin panel |
|
||||||
|
| `rust_core/src/lib.rs` | ~169 | FFI exports (all `rgl_*` functions) |
|
||||||
|
| `rust_core/src/server.rs` | ~306 | Axum HTTP/WS server, routing, static files |
|
||||||
|
| `rust_core/src/bridge.rs` | ~149 | Lua↔Rust request/response bridge + event broadcast |
|
||||||
|
| `rust_core/src/db.rs` | ~397 | SQLite key-value store with async batch API |
|
||||||
|
| `rust_core/src/events.rs` | ~77 | Win-1251↔UTF-8 conversion, SA-MP color parsing |
|
||||||
|
| `rust_core/src/logging.rs` | ~45 | File + WS broadcast logging |
|
||||||
|
| `rust_core/static/ui_page.html` | ~293 | Generic auto-UI renderer for modules with render() |
|
||||||
|
| `rust_core/static/index.html` | ~55 | Dashboard page |
|
||||||
|
|
||||||
|
### Known Issues
|
||||||
|
- `.gitignore` is minimal — missing `target/`, `*.so`, `*.log`, `rgl_data.db`
|
||||||
|
- Broadcast channel (capacity 256) silently drops events when full
|
||||||
|
- Fallback JSON encoder only handles flat `{"key":"value"}` — cjson must be present
|
||||||
|
|
||||||
|
### Additional Documentation
|
||||||
|
- `docs/MODULE_GUIDE.md` — complete module authoring reference with examples
|
||||||
|
- `docs/LIB_REFERENCE.md` — analysis of available standard libraries (../lib)
|
||||||
|
- `docs/TASKS.md` — prioritized improvement backlog
|
||||||
@ -0,0 +1,205 @@
|
|||||||
|
# Standard Library Reference
|
||||||
|
|
||||||
|
Analysis of the shared Lua library at `../lib` (`/home/regela/Workspace/lua/lib/`). Contains ~163 files across 17 directories (6.4MB total).
|
||||||
|
|
||||||
|
## Currently Used by Project
|
||||||
|
|
||||||
|
| Module | Import | Purpose |
|
||||||
|
|--------|--------|---------|
|
||||||
|
| `ffi` | Built-in LuaJIT | FFI to load libarz_core.so, imgui bindings |
|
||||||
|
| `encoding` | `require("encoding")` | UTF-8 / CP1251 conversion |
|
||||||
|
| `cjson` | `require("cjson")` | JSON encode/decode |
|
||||||
|
| `mimgui` | `require("mimgui")` | ImGui binding for in-game UI |
|
||||||
|
| `samp.events` | `require("samp.events")` | SA-MP event hooking (RPC/packets) |
|
||||||
|
| `lua_thread` | Global | Coroutine-based threading (MonetLoader built-in) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## High-Value Unused Libraries
|
||||||
|
|
||||||
|
### SAMemory — Game Memory Access
|
||||||
|
**Path:** `lib/SAMemory/`
|
||||||
|
**Size:** 50+ files covering all game entity types
|
||||||
|
|
||||||
|
Direct FFI-based access to GTA SA game structures: CPed, CVehicle, CEntity, CCamera, CWorld, etc. Read player position, health, vehicle state, weapon data directly from memory.
|
||||||
|
|
||||||
|
**Use cases for modules:**
|
||||||
|
- Read player coordinates for location-based automation
|
||||||
|
- Check vehicle state (speed, health, passengers)
|
||||||
|
- Access weapon/ammo data
|
||||||
|
- Read game time, weather, gravity
|
||||||
|
|
||||||
|
**Key files:**
|
||||||
|
- `SAMemory/init.lua` — main entry point
|
||||||
|
- `SAMemory/game/CPed.lua` — player/NPC structures
|
||||||
|
- `SAMemory/game/CVehicle.lua` — vehicle structures
|
||||||
|
- `SAMemory/game/CCamera.lua` — camera state
|
||||||
|
|
||||||
|
### requests.lua — HTTP Client
|
||||||
|
**Path:** `lib/requests.lua` (~270 lines)
|
||||||
|
|
||||||
|
Full HTTP/HTTPS client built on LuaSocket + LuaSec. Supports:
|
||||||
|
- GET, POST, PUT, DELETE methods
|
||||||
|
- Basic and Digest authentication
|
||||||
|
- Automatic HTTPS
|
||||||
|
- Cookie handling
|
||||||
|
- Custom headers
|
||||||
|
|
||||||
|
**Use cases:** External API calls, webhooks, data sync with remote servers.
|
||||||
|
|
||||||
|
### inicfg.lua — INI Config Files
|
||||||
|
**Path:** `lib/inicfg.lua` (~150 lines)
|
||||||
|
|
||||||
|
Read/write `.ini` config files with sections. Auto-creates defaults.
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local inicfg = require("inicfg")
|
||||||
|
local cfg = inicfg.load({section = {key = "default"}}, "mymod.ini")
|
||||||
|
cfg.section.key = "new_value"
|
||||||
|
inicfg.save(cfg, "mymod.ini")
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note:** The framework already has DB persistence, but inicfg is useful for human-editable config files.
|
||||||
|
|
||||||
|
### jsoncfg.lua — JSON Config Files
|
||||||
|
**Path:** `lib/jsoncfg.lua` (~150 lines)
|
||||||
|
|
||||||
|
Similar to inicfg but uses JSON format. Supports deep merge of defaults.
|
||||||
|
|
||||||
|
### game/ — Game Constants
|
||||||
|
**Path:** `lib/game/`
|
||||||
|
|
||||||
|
| File | Content |
|
||||||
|
|------|---------|
|
||||||
|
| `globals.lua` (~650 lines) | Script variables $0-$2000 |
|
||||||
|
| `models.lua` (~500 lines) | Object/vehicle model IDs |
|
||||||
|
| `weapons.lua` (~100 lines) | Weapon IDs and names |
|
||||||
|
| `keys.lua` (~50 lines) | Control key constants |
|
||||||
|
|
||||||
|
**Use cases:** Reference weapon names, vehicle model IDs, key bindings in modules.
|
||||||
|
|
||||||
|
### Crypto & Encoding
|
||||||
|
|
||||||
|
| Module | Path | Purpose |
|
||||||
|
|--------|------|---------|
|
||||||
|
| `md5.lua` | `lib/md5.lua` | MD5 hashing |
|
||||||
|
| `sha1/` | `lib/sha1/` | SHA-1 hashing + HMAC (multiple implementations) |
|
||||||
|
| `basexx.lua` | `lib/basexx.lua` | Base16, Base32, Base64 encoding/decoding |
|
||||||
|
|
||||||
|
**Use cases:** Data integrity checks, API signatures, obfuscation.
|
||||||
|
|
||||||
|
### copas.lua — Async I/O
|
||||||
|
**Path:** `lib/copas.lua` (~1500 lines) + submodules
|
||||||
|
|
||||||
|
Coroutine-based async TCP/IP dispatcher. Non-blocking HTTP, FTP, SMTP.
|
||||||
|
|
||||||
|
Submodules:
|
||||||
|
- `copas/http.lua` — async HTTP client
|
||||||
|
- `copas/smtp.lua` — async email sending
|
||||||
|
- `copas/ftp.lua` — async FTP
|
||||||
|
- `copas/timer.lua` — timer support
|
||||||
|
- `copas/lock.lua`, `queue.lua`, `semaphore.lua` — async primitives
|
||||||
|
|
||||||
|
**Note:** The framework already uses Rust's tokio for async. Copas is an alternative for pure-Lua async if needed.
|
||||||
|
|
||||||
|
### timerwheel.lua — Efficient Timers
|
||||||
|
**Path:** `lib/timerwheel.lua` (~300 lines)
|
||||||
|
|
||||||
|
O(1) timer wheel for managing many concurrent timers efficiently. Better than individual `lua_thread` coroutines when you have dozens of timers.
|
||||||
|
|
||||||
|
### monethook.lua — Function Hooking
|
||||||
|
**Path:** `lib/monethook.lua` (~80 lines)
|
||||||
|
|
||||||
|
Inline function hooking for MonetLoader >= 2.8.0. Intercept game function calls at the assembly level.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Medium-Value Libraries
|
||||||
|
|
||||||
|
### Math
|
||||||
|
| Module | Path | Purpose |
|
||||||
|
|--------|------|---------|
|
||||||
|
| `vector3d.lua` | `lib/vector3d.lua` | 3D vector ops (normalize, dot, cross) |
|
||||||
|
| `matrix3x3.lua` | `lib/matrix3x3.lua` | 3x3 matrix ops |
|
||||||
|
|
||||||
|
### Networking Stack
|
||||||
|
| Module | Path | Purpose |
|
||||||
|
|--------|------|---------|
|
||||||
|
| `socket.lua` | `lib/socket.lua` | LuaSocket TCP wrapper |
|
||||||
|
| `ssl.lua` | `lib/ssl.lua` | LuaSec TLS/SSL |
|
||||||
|
| `socket/http.lua` | `lib/socket/http.lua` | Low-level HTTP client |
|
||||||
|
| `socket/url.lua` | `lib/socket/url.lua` | URL parsing (RFC 3986) |
|
||||||
|
| `socket/ftp.lua` | `lib/socket/ftp.lua` | FTP client |
|
||||||
|
| `socket/smtp.lua` | `lib/socket/smtp.lua` | SMTP client |
|
||||||
|
| `socket/headers.lua` | `lib/socket/headers.lua` | HTTP header utils |
|
||||||
|
|
||||||
|
Prefer `requests.lua` over raw socket modules for HTTP.
|
||||||
|
|
||||||
|
### Data Processing
|
||||||
|
| Module | Path | Purpose |
|
||||||
|
|--------|------|---------|
|
||||||
|
| `ltn12.lua` | `lib/ltn12.lua` | Source/sink/pump data streaming |
|
||||||
|
| `mime.lua` | `lib/mime.lua` | Base64, quoted-printable MIME encoding |
|
||||||
|
| `binaryheap.lua` | `lib/binaryheap.lua` | Priority queue (O(log n)) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Low-Value / Specialized
|
||||||
|
|
||||||
|
### Android-Specific
|
||||||
|
| Module | Path | Purpose |
|
||||||
|
|--------|------|---------|
|
||||||
|
| `android/arizona.lua` | MonetLoader Arizona RP API | Only on Android |
|
||||||
|
| `android/jnienv.lua` | JNI environment helpers | Java/Lua interop |
|
||||||
|
| `android/jni-raw.lua` | Raw JNI access | Low-level |
|
||||||
|
| `android/jar/arzapi.jar` | Arizona API JAR | Binary |
|
||||||
|
|
||||||
|
### Platform / Runtime
|
||||||
|
| Module | Path | Purpose |
|
||||||
|
|--------|------|---------|
|
||||||
|
| `MoonMonet/` | Material Design 3 color generation | Theming |
|
||||||
|
| `monetloader.lua` | MonetLoader constants | TAG types |
|
||||||
|
| `moonloader.lua` | MoonLoader compat layer | Logging codes |
|
||||||
|
| `sa_renderfix.lua` | Screen scaling fixes | Rendering |
|
||||||
|
| `bitex.lua` | Bit extraction utilities | Bit manipulation |
|
||||||
|
| `widgets.lua` | SAMP widget constants | Vehicle/player keys |
|
||||||
|
| `sampfuncs.lua` | RPC/packet ID constants | SAMP protocol |
|
||||||
|
|
||||||
|
### UI
|
||||||
|
| Module | Path | Purpose |
|
||||||
|
|--------|------|---------|
|
||||||
|
| `imgui_piemenu.lua` | Circular pie menu for imgui | Alternative menu |
|
||||||
|
| `webviews/` | WebView integration | Android WebView + JAR |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## SAMP Event System Deep Dive
|
||||||
|
|
||||||
|
**Path:** `lib/samp/`
|
||||||
|
|
||||||
|
The framework hooks all SAMP events via `samp.events`. Key files:
|
||||||
|
|
||||||
|
| File | Purpose |
|
||||||
|
|------|---------|
|
||||||
|
| `samp/events.lua` | Main entry point, event registration |
|
||||||
|
| `samp/events/core.lua` | Event dispatcher (~200 lines) |
|
||||||
|
| `samp/events/handlers.lua` | Specialized RPC handlers (~150 lines) |
|
||||||
|
| `samp/events/bitstream_io.lua` | Binary data serialization (~300 lines) |
|
||||||
|
| `samp/events/utils.lua` | Helper functions |
|
||||||
|
| `samp/events/extra_types.lua` | Extended data types |
|
||||||
|
| `samp/raknet.lua` | RakNet protocol constants |
|
||||||
|
| `samp/synchronization.lua` | Player/vehicle sync state |
|
||||||
|
| `sampfuncs.lua` | RPC and packet ID constants |
|
||||||
|
|
||||||
|
The framework's `setup_event_hooks()` iterates over all entries in `sampev.INTERFACE` (OUTCOMING_RPCS, INCOMING_RPCS, OUTCOMING_PACKETS, INCOMING_PACKETS) and creates hooks for each event name. This gives modules access to all ~200+ SAMP events without manual registration.
|
||||||
|
|
||||||
|
## Summary: Recommended for Integration
|
||||||
|
|
||||||
|
Priority order for adding to new modules:
|
||||||
|
|
||||||
|
1. **SAMemory** — most powerful, enables location/state-aware automation
|
||||||
|
2. **game/*** — essential constants for any game-interacting module
|
||||||
|
3. **requests.lua** — if modules need external HTTP APIs
|
||||||
|
4. **md5/sha1/basexx** — if data integrity or encoding needed
|
||||||
|
5. **inicfg/jsoncfg** — if human-editable config files needed
|
||||||
|
6. **timerwheel** — if module manages many concurrent timers
|
||||||
@ -0,0 +1,508 @@
|
|||||||
|
# Module Authoring Guide
|
||||||
|
|
||||||
|
Guide for developing modules for the ARZ Web Helper framework.
|
||||||
|
|
||||||
|
## Module Structure
|
||||||
|
|
||||||
|
A module is a directory containing at minimum `init.lua`:
|
||||||
|
|
||||||
|
```
|
||||||
|
modules/mymodule/
|
||||||
|
├── init.lua -- Required: module entry point
|
||||||
|
└── static/ -- Optional: custom web UI
|
||||||
|
├── index.html
|
||||||
|
├── style.css
|
||||||
|
└── app.js
|
||||||
|
```
|
||||||
|
|
||||||
|
Modules placed in `http_framework/modules/` are bundled with the framework. Modules in `more_modules/` are separate (gitignored). Both are loaded identically at runtime.
|
||||||
|
|
||||||
|
## Module Interface
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
function M.init(fw)
|
||||||
|
-- Required. Called once when module is loaded.
|
||||||
|
-- fw: framework API object (namespaced per module)
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.render(ui, state)
|
||||||
|
-- Optional. Generates UI for both web and in-game imgui.
|
||||||
|
-- ui: abstract widget builder
|
||||||
|
-- state: persistent table (survives re-renders, cleared on module reset)
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.unload()
|
||||||
|
-- Optional. Cleanup when module is unloaded/reloaded.
|
||||||
|
-- Cancel timers, reset globals, release resources.
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
|
```
|
||||||
|
|
||||||
|
## Framework API (fw object)
|
||||||
|
|
||||||
|
### Logging
|
||||||
|
|
||||||
|
```lua
|
||||||
|
fw.log(level, tag, message, ...)
|
||||||
|
-- level: "INFO", "WARN", "ERROR", "DEBUG"
|
||||||
|
-- tag: short identifier (e.g. module name)
|
||||||
|
-- Additional args are concatenated with spaces
|
||||||
|
-- Also broadcasts to WebSocket clients as __log event
|
||||||
|
```
|
||||||
|
|
||||||
|
### JSON
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local tbl = fw.json_decode(json_string)
|
||||||
|
local str = fw.json_encode(table)
|
||||||
|
-- Uses cjson if available, falls back to naive parser
|
||||||
|
-- Fallback only handles flat {"key":"value"} — always ensure cjson is present
|
||||||
|
```
|
||||||
|
|
||||||
|
### Encoding (Win-1251 / UTF-8)
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local utf8 = fw.to_utf8(win1251_string)
|
||||||
|
local win = fw.to_win1251(utf8_string)
|
||||||
|
-- Game uses CP1251 for Cyrillic text
|
||||||
|
-- Web and DB use UTF-8
|
||||||
|
-- Convert before sending to game APIs (sampProcessChatInput, etc.)
|
||||||
|
```
|
||||||
|
|
||||||
|
### API Handlers
|
||||||
|
|
||||||
|
Register HTTP endpoints accessible at `POST /api/{module}/{action}`:
|
||||||
|
|
||||||
|
```lua
|
||||||
|
fw.on_api("mymod", "do_something", function(body)
|
||||||
|
-- body: raw request body string (JSON)
|
||||||
|
local data = fw.json_decode(body)
|
||||||
|
-- ... process ...
|
||||||
|
return fw.json_encode({ok = true, result = "done"})
|
||||||
|
end)
|
||||||
|
```
|
||||||
|
|
||||||
|
The handler receives the POST body as a string and must return a JSON string. Errors in handlers are caught by the framework and returned as `{"error":"..."}`.
|
||||||
|
|
||||||
|
Timeout: 2 seconds. If the game thread is blocked, the request returns `{"error":"lua timeout"}`.
|
||||||
|
|
||||||
|
### Event Interception
|
||||||
|
|
||||||
|
Hook into SA-MP network events:
|
||||||
|
|
||||||
|
```lua
|
||||||
|
fw.on_event(event_name, handler, priority)
|
||||||
|
-- event_name: SAMP event (e.g. "onServerMessage", "onShowDialog", "onSendChat")
|
||||||
|
-- handler: function receiving event arguments
|
||||||
|
-- priority: lower = fires first (default 50)
|
||||||
|
-- Return false to block the event from propagating
|
||||||
|
-- Return nil/true to allow propagation
|
||||||
|
```
|
||||||
|
|
||||||
|
Example — block outgoing messages containing "spam":
|
||||||
|
```lua
|
||||||
|
fw.on_event("onSendChat", function(message)
|
||||||
|
if message:lower():find("spam") then
|
||||||
|
fw.log("WARN", "MYMOD", "Blocked: " .. message)
|
||||||
|
return false -- cancel the event
|
||||||
|
end
|
||||||
|
end, 10) -- priority 10: runs before default (50)
|
||||||
|
```
|
||||||
|
|
||||||
|
Common events:
|
||||||
|
- `onServerMessage(color, text)` — incoming chat message
|
||||||
|
- `onSendChat(message)` — outgoing chat message
|
||||||
|
- `onShowDialog(id, style, title, button1, button2, text)` — server dialog
|
||||||
|
- All SAMP RPCs and packets from `samp.events.INTERFACE`
|
||||||
|
|
||||||
|
Events are also broadcast to WebSocket clients as JSON:
|
||||||
|
```json
|
||||||
|
{"type":"event", "event":"onServerMessage", "args":[color, text]}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Commands
|
||||||
|
|
||||||
|
```lua
|
||||||
|
fw.command("mycommand", function(args)
|
||||||
|
-- args: string after the command (e.g. "/mycommand hello world" → "hello world")
|
||||||
|
fw.add_chat_message("You said: " .. args, -1)
|
||||||
|
end)
|
||||||
|
-- Registers /mycommand with SA-MP and the framework's command list
|
||||||
|
-- Visible in admin panel Commands tab
|
||||||
|
```
|
||||||
|
|
||||||
|
### Chat / Game Interaction
|
||||||
|
|
||||||
|
```lua
|
||||||
|
fw.chat(text)
|
||||||
|
-- Process text as chat input (sends message or executes /command)
|
||||||
|
|
||||||
|
fw.add_chat_message(text, color)
|
||||||
|
-- Show a local-only chat message (not sent to server)
|
||||||
|
-- color: ARGB integer or -1 for default white
|
||||||
|
|
||||||
|
fw.respond_dialog(id, button, listbox_id, input)
|
||||||
|
-- Send a dialog response to the server
|
||||||
|
-- id: dialog ID, button: 0=close/1=confirm, listbox_id: selected row, input: text
|
||||||
|
|
||||||
|
fw.unlock_dialog()
|
||||||
|
-- Sends /mm then auto-closes the menu dialog (id=722)
|
||||||
|
-- Used to unblock the dialog system after automated dialog interaction
|
||||||
|
```
|
||||||
|
|
||||||
|
### Timers
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local cancel = fw.timer_once(milliseconds, callback)
|
||||||
|
-- Schedules a one-shot callback after delay
|
||||||
|
-- Returns a cancel function: call cancel() to abort
|
||||||
|
-- Runs in a separate lua_thread coroutine
|
||||||
|
-- Use for deferred dialog responses, delayed actions
|
||||||
|
```
|
||||||
|
|
||||||
|
Pattern — deferred dialog response (from btc module):
|
||||||
|
```lua
|
||||||
|
fw.on_event("onShowDialog", function(id, style, title, b1, b2, text)
|
||||||
|
if id == 7238 then
|
||||||
|
fw.timer_once(5, function()
|
||||||
|
fw.respond_dialog(id, 1, 0, "")
|
||||||
|
end)
|
||||||
|
return false -- hide dialog from player
|
||||||
|
end
|
||||||
|
end, 20)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Database (Key-Value, Auto-Namespaced)
|
||||||
|
|
||||||
|
All keys are automatically prefixed with `"{module}."` for isolation between modules.
|
||||||
|
|
||||||
|
#### Simple Operations
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local value = fw.db_get(key) -- Returns string or nil
|
||||||
|
local ok = fw.db_set(key, value) -- Returns true/false
|
||||||
|
local ok = fw.db_delete(key) -- Returns true/false
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Batch Operations (Recommended for Multiple Keys)
|
||||||
|
|
||||||
|
```lua
|
||||||
|
-- Get multiple keys at once
|
||||||
|
local results = fw.db_get_many({"key1", "key2", "key3"})
|
||||||
|
-- Returns: {key1 = "val1", key2 = "val2"} (missing keys omitted)
|
||||||
|
|
||||||
|
-- Set multiple keys at once
|
||||||
|
fw.db_set_many({key1 = "val1", key2 = "val2", key3 = "val3"})
|
||||||
|
|
||||||
|
-- Get all keys with a prefix
|
||||||
|
local items = fw.db_get_prefix("settings.")
|
||||||
|
-- Returns: {["settings.delay"] = "5", ["settings.mode"] = "auto"}
|
||||||
|
```
|
||||||
|
|
||||||
|
These batch methods use `fw.db_batch()` internally, which sends operations to the async SQLite backend. They block until results are available (safe in coroutines with `wait()`).
|
||||||
|
|
||||||
|
#### Persistence Patterns
|
||||||
|
|
||||||
|
Settings pattern (from btc module):
|
||||||
|
```lua
|
||||||
|
local settings = {delay = 5, mode = "auto"}
|
||||||
|
|
||||||
|
local function save_settings()
|
||||||
|
local kv = {}
|
||||||
|
for k, v in pairs(settings) do kv["s." .. k] = tostring(v) end
|
||||||
|
fw.db_set_many(kv)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function load_settings()
|
||||||
|
local all = fw.db_get_prefix("s.")
|
||||||
|
for k, default in pairs(settings) do
|
||||||
|
local val = all["s." .. k]
|
||||||
|
if val then
|
||||||
|
if type(default) == "number" then settings[k] = tonumber(val) or default
|
||||||
|
else settings[k] = val end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
### Module Registration
|
||||||
|
|
||||||
|
```lua
|
||||||
|
fw.register_module(name, static_dir)
|
||||||
|
-- Register a module's static file directory for web serving at /m/{name}/
|
||||||
|
-- static_dir: absolute path to directory containing index.html etc.
|
||||||
|
-- Use fw.modules_dir .. "/mymod/static" for the standard path
|
||||||
|
```
|
||||||
|
|
||||||
|
### Other
|
||||||
|
|
||||||
|
```lua
|
||||||
|
fw.modules_dir -- Path to the modules directory (string)
|
||||||
|
fw.rust -- Direct FFI reference to libarz_core.so (advanced use only)
|
||||||
|
fw.push_event(name, json_args) -- Broadcast custom event to WebSocket clients
|
||||||
|
fw.fire_event(event_name, ...) -- Manually trigger event interceptors
|
||||||
|
```
|
||||||
|
|
||||||
|
## UI Rendering (render function)
|
||||||
|
|
||||||
|
Modules can provide a `render(ui, state)` function for automatic UI generation. The same code renders both in the web browser (via JSON widgets) and in-game (via mimgui/ImGui).
|
||||||
|
|
||||||
|
### How It Works
|
||||||
|
|
||||||
|
1. Module defines `M.render(ui, state)`
|
||||||
|
2. Framework detects `render()` and registers the module as `__render__`
|
||||||
|
3. Web requests to `/m/{module}/` serve a generic UI page (`ui_page.html`)
|
||||||
|
4. The page periodically calls `POST /api/{module}/__render` with user interactions
|
||||||
|
5. Framework calls `M.render()` with a UI builder that records widgets as JSON
|
||||||
|
6. Browser renders the JSON widget tree as HTML
|
||||||
|
|
||||||
|
For imgui: the framework calls `M.render()` with an imgui wrapper that translates widget calls to real ImGui commands.
|
||||||
|
|
||||||
|
### Widget API
|
||||||
|
|
||||||
|
#### Text & Layout
|
||||||
|
|
||||||
|
```lua
|
||||||
|
ui.text("Plain text")
|
||||||
|
ui.text_colored(r, g, b, a, "Colored text") -- RGBA 0.0-1.0
|
||||||
|
ui.separator() -- Horizontal line
|
||||||
|
ui.spacing() -- Vertical gap
|
||||||
|
ui.sameline() -- Next widget on same line
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Interactive Widgets
|
||||||
|
|
||||||
|
```lua
|
||||||
|
-- Button: returns true on the frame it was clicked
|
||||||
|
if ui.button("Click me") then
|
||||||
|
-- handle click
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Text input: returns current value
|
||||||
|
local name = ui.input("Label", current_value)
|
||||||
|
|
||||||
|
-- Integer input
|
||||||
|
local count = ui.input_int("Count", current_value)
|
||||||
|
|
||||||
|
-- Checkbox: returns boolean
|
||||||
|
local enabled = ui.checkbox("Enable feature", current_bool)
|
||||||
|
|
||||||
|
-- Integer slider
|
||||||
|
local level = ui.slider_int("Level", current_value, min, max)
|
||||||
|
|
||||||
|
-- Float slider
|
||||||
|
local alpha = ui.slider_float("Alpha", current_value, 0, 1)
|
||||||
|
|
||||||
|
-- Dropdown
|
||||||
|
local idx = ui.combo("Choose", selected_index, {"Option A", "Option B", "Option C"})
|
||||||
|
-- selected_index is 0-based
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Progress
|
||||||
|
|
||||||
|
```lua
|
||||||
|
ui.progress(0.75, "75%") -- fraction 0.0-1.0
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Containers
|
||||||
|
|
||||||
|
```lua
|
||||||
|
-- Collapsible section
|
||||||
|
if ui.collapsing("Details") then
|
||||||
|
ui.text("Hidden content")
|
||||||
|
ui.collapsing_end()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Tab bar with tabs
|
||||||
|
if ui.tab_bar("MyTabs") then
|
||||||
|
if ui.tab_item("Tab 1") then
|
||||||
|
ui.text("Tab 1 content")
|
||||||
|
ui.tab_end()
|
||||||
|
end
|
||||||
|
if ui.tab_item("Tab 2") then
|
||||||
|
ui.text("Tab 2 content")
|
||||||
|
ui.tab_end()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
### State Persistence
|
||||||
|
|
||||||
|
The `state` table passed to `render()` persists between renders (it's stored in `module_states[name]`). Use it for UI state that doesn't need database persistence:
|
||||||
|
|
||||||
|
```lua
|
||||||
|
function M.render(ui, state)
|
||||||
|
state.counter = state.counter or 0
|
||||||
|
ui.text("Count: " .. state.counter)
|
||||||
|
if ui.button("Increment") then
|
||||||
|
state.counter = state.counter + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
State is cleared on module reset (admin panel "Reset" button).
|
||||||
|
|
||||||
|
### Interactions Model
|
||||||
|
|
||||||
|
The web UI sends interactions as a JSON object mapping widget IDs to values:
|
||||||
|
- Buttons: `{id: "click"}`
|
||||||
|
- Inputs: `{id: "new text value"}`
|
||||||
|
- Checkboxes: `{id: "true"/"false"}`
|
||||||
|
- Sliders/combos: `{id: "number_as_string"}`
|
||||||
|
|
||||||
|
Widget IDs are auto-generated (`b1`, `i2`, `c3`, etc.) in order of creation. This means **widget order must be stable** — conditional widgets that change position will break ID mapping.
|
||||||
|
|
||||||
|
## Module Patterns
|
||||||
|
|
||||||
|
### Pattern 1: Minimal Module (API only)
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
function M.init(fw)
|
||||||
|
fw.on_api("ping", "check", function()
|
||||||
|
return '{"ok":true,"time":' .. os.time() .. '}'
|
||||||
|
end)
|
||||||
|
fw.command("ping", function()
|
||||||
|
fw.add_chat_message("Pong!", -1)
|
||||||
|
end)
|
||||||
|
fw.log("INFO", "PING", "Module loaded")
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pattern 2: Auto-UI Module (render)
|
||||||
|
|
||||||
|
No static files needed. Framework generates the web page automatically.
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local M = {}
|
||||||
|
local fw
|
||||||
|
|
||||||
|
function M.init(f)
|
||||||
|
fw = f
|
||||||
|
fw.log("INFO", "MYMOD", "Loaded")
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.render(ui, state)
|
||||||
|
state.name = state.name or ""
|
||||||
|
state.name = ui.input("Your name", state.name)
|
||||||
|
|
||||||
|
if ui.button("Greet") then
|
||||||
|
fw.add_chat_message("Hello, " .. state.name .. "!", -1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pattern 3: Custom HTML Module
|
||||||
|
|
||||||
|
For rich UIs beyond what the widget builder offers.
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
function M.init(fw)
|
||||||
|
local static_dir = fw.modules_dir .. "/mymod/static"
|
||||||
|
fw.register_module("mymod", static_dir)
|
||||||
|
|
||||||
|
fw.on_api("mymod", "getData", function()
|
||||||
|
return fw.json_encode({items = {"a", "b", "c"}})
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
|
```
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- modules/mymod/static/index.html -->
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head><title>My Module</title></head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script>
|
||||||
|
// Connect to WebSocket for real-time events
|
||||||
|
const ws = new WebSocket('ws://' + location.host + '/ws');
|
||||||
|
ws.onmessage = (e) => {
|
||||||
|
const msg = JSON.parse(e.data);
|
||||||
|
if (msg.event === 'onServerMessage') {
|
||||||
|
// Handle chat message
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Call module API
|
||||||
|
async function loadData() {
|
||||||
|
const res = await fetch('/api/mymod/getData', {method: 'POST'});
|
||||||
|
const data = await res.json();
|
||||||
|
document.getElementById('app').textContent = JSON.stringify(data);
|
||||||
|
}
|
||||||
|
loadData();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pattern 4: Event Interceptor with Dialog Automation
|
||||||
|
|
||||||
|
From the btc module — intercept server dialogs, parse content, respond automatically:
|
||||||
|
|
||||||
|
```lua
|
||||||
|
function M.init(fw)
|
||||||
|
fw.on_event("onShowDialog", function(id, style, title, b1, b2, text)
|
||||||
|
if id == 12345 then -- your target dialog ID
|
||||||
|
-- Parse dialog text
|
||||||
|
for line in text:gmatch("[^\n]+") do
|
||||||
|
local value = line:match("Balance: (%d+)")
|
||||||
|
if value then
|
||||||
|
fw.log("INFO", "MYMOD", "Balance: " .. value)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- Auto-respond after small delay
|
||||||
|
fw.timer_once(5, function()
|
||||||
|
fw.respond_dialog(id, 1, 0, "") -- click OK
|
||||||
|
end)
|
||||||
|
return false -- hide dialog from player
|
||||||
|
end
|
||||||
|
end, 20) -- priority 20
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
Key points:
|
||||||
|
- Dialog IDs are hardcoded SA-MP server values — find them by logging all dialogs first
|
||||||
|
- Always use `fw.timer_once()` for dialog responses to avoid blocking the game thread
|
||||||
|
- Return `false` to hide the dialog from the player
|
||||||
|
- Use `fw.unlock_dialog()` after a chain of automated dialogs to unblock the dialog system
|
||||||
|
- Dialog text uses Win-1251 encoding — use hex escapes for Cyrillic matching (e.g. `\xc2\xea\xeb\xfe\xf7` for "Включ")
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
Module errors are caught by the framework via `pcall()`. If a module crashes:
|
||||||
|
- Error is logged via `fw.log("ERROR", ...)`
|
||||||
|
- Toast notification appears in-game
|
||||||
|
- Error is stored and visible in admin panel
|
||||||
|
- Module continues to be loaded (other handlers still work)
|
||||||
|
|
||||||
|
Best practices:
|
||||||
|
- Use `pcall()` for operations that might fail (JSON decode, game API calls)
|
||||||
|
- Always validate API body data before using it
|
||||||
|
- Log meaningful error messages with context
|
||||||
|
- Implement `M.unload()` to clean up state (cancel timers, reset globals)
|
||||||
|
|
||||||
|
## Hot Reloading
|
||||||
|
|
||||||
|
Modules can be reloaded at runtime via the admin panel or API:
|
||||||
|
- **Reload**: calls `unload()`, removes all API handlers and event interceptors owned by module, then loads fresh
|
||||||
|
- **Reset**: same as reload but also deletes all module DB data and clears state
|
||||||
|
|
||||||
|
On reload, all API handlers and event interceptors registered by the module are automatically removed before re-init. This means modules don't need to manually clean up handlers.
|
||||||
|
|
||||||
|
Things that are NOT auto-cleaned on reload:
|
||||||
|
- Global variables (avoid using them)
|
||||||
|
- SA-MP chat commands (registered once, cannot be unregistered)
|
||||||
|
- Running `lua_thread` coroutines from `fw.timer_once()`
|
||||||
@ -0,0 +1,105 @@
|
|||||||
|
# Tasks & Improvements
|
||||||
|
|
||||||
|
Prioritized backlog of issues, improvements, and feature ideas.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Critical
|
||||||
|
|
||||||
|
*No critical issues currently.*
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## High
|
||||||
|
|
||||||
|
### Add proper .gitignore
|
||||||
|
Current `.gitignore` only has `more_modules`. Missing:
|
||||||
|
- `rust_core/target/`
|
||||||
|
- `*.so`
|
||||||
|
- `rgl_data.db`
|
||||||
|
- `*.log`
|
||||||
|
- `.DS_Store`
|
||||||
|
|
||||||
|
### Create build script
|
||||||
|
No Makefile or build automation exists. Need a script for:
|
||||||
|
- Cross-compile Rust for aarch64-linux-android
|
||||||
|
- Deploy .so to device via ADB
|
||||||
|
- Optional: rebuild on file change
|
||||||
|
|
||||||
|
### Improve Rust error handling
|
||||||
|
50+ `unwrap()` calls across Rust codebase. Key areas:
|
||||||
|
- `bridge.rs`: mutex locks can panic on poisoning — use `unwrap_or_else()` with recovery
|
||||||
|
- `db.rs`: 30+ unwrap/unwrap_or_default — silent failures on DB errors
|
||||||
|
- `server.rs`: 15+ unwraps in HTTP handlers
|
||||||
|
Should introduce proper error types (thiserror crate) or at minimum `unwrap_or_else()` with logging.
|
||||||
|
|
||||||
|
### Silent JSON parse failures in db.rs
|
||||||
|
`execute_batch()` returns `"[]"` on JSON parse error without logging. Should log the error for debugging.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Medium
|
||||||
|
|
||||||
|
### Fix BTC module global state
|
||||||
|
`more_modules/btc/init.lua` uses `btc_visible = false` as a global variable (for mimgui OnFrame). Should be moved to a proper module state mechanism to avoid global namespace pollution.
|
||||||
|
|
||||||
|
### Implement WebSocket backpressure
|
||||||
|
`bridge.rs` broadcast channel has capacity 256. Events are silently dropped when full. Should either increase capacity, add warning logging, or implement backpressure.
|
||||||
|
|
||||||
|
### Improve fallback JSON encoder
|
||||||
|
`rgl_framework.lua` fallback JSON decoder only handles flat `{"key":"value"}` — fails on nested objects, arrays, numbers, booleans. Since cjson is always expected to be present, consider making it a hard requirement instead of silently degrading.
|
||||||
|
|
||||||
|
### Add integration tests
|
||||||
|
Only `db.rs` has tests (6 batch operation tests). Missing:
|
||||||
|
- Bridge request/response cycle
|
||||||
|
- Event system overflow/blocking
|
||||||
|
- HTTP handler edge cases
|
||||||
|
- Module loading/unloading
|
||||||
|
- Win-1251 encoding conversion
|
||||||
|
|
||||||
|
### Module loading error handling
|
||||||
|
`load_all_modules()` uses `io.popen('ls ...')` which can fail if directory is deleted between listing and loading. Use `pcall(io.open)` instead.
|
||||||
|
|
||||||
|
### Add auth/CORS for web API
|
||||||
|
Currently any network client can call all APIs. Consider at minimum:
|
||||||
|
- Bind to localhost only (or configurable)
|
||||||
|
- Basic auth token
|
||||||
|
- CORS headers for web clients
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Low
|
||||||
|
|
||||||
|
### Add rate limiting to API handlers
|
||||||
|
No protection against API spam. Could add simple per-endpoint rate limits.
|
||||||
|
|
||||||
|
### Add request/response logging middleware
|
||||||
|
No HTTP access logging in Rust. Add optional access log for debugging API calls.
|
||||||
|
|
||||||
|
### Optimize broadcast channel capacity
|
||||||
|
Current 256 capacity is arbitrary. Profile actual event rates and set appropriately.
|
||||||
|
|
||||||
|
### Add module versioning
|
||||||
|
No way to track which version of a module is loaded. Could add `M.version` field and display in admin panel.
|
||||||
|
|
||||||
|
### Add deployment documentation
|
||||||
|
No docs on how to deploy the .so to an Android device, set up the framework, or configure MoonLoader to load the script.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Feature Ideas
|
||||||
|
|
||||||
|
### WebSocket command execution
|
||||||
|
Allow web clients to send commands via WebSocket instead of only HTTP POST. Would enable real-time bidirectional communication.
|
||||||
|
|
||||||
|
### Module dependency system
|
||||||
|
Currently modules load independently. A dependency system would let modules declare required modules and load in order.
|
||||||
|
|
||||||
|
### Persistent notification history
|
||||||
|
In-game notifications disappear after timeout. Could persist to DB and show in admin panel.
|
||||||
|
|
||||||
|
### SAMemory integration module
|
||||||
|
Create a utility module that exposes player position, health, vehicle state via the web UI using the SAMemory library from ../lib.
|
||||||
|
|
||||||
|
### Config file support
|
||||||
|
Use `inicfg.lua` or `jsoncfg.lua` from ../lib for human-editable configuration alongside the DB-based persistence.
|
||||||
Loading…
Reference in new issue