You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
141 lines
7.7 KiB
141 lines
7.7 KiB
|
1 day ago
|
# 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
|