7.7 KiB
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.sovia 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)
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
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'smain()loop callsrgl_poll()each frame, executes the code, and returns results viargl_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 viaui_page.html).create_ui_imgui()(rgl_framework.lua:267) — wrapsmimgui(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 modulesGET /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 modulesGET /api/commands— JSON list of registered commandsGET /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
falseto hide dialog from player. - Event interceptors sorted by priority (lower = earlier). Return
falseto 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\xf7for "Включ").
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
.gitignoreis minimal — missingtarget/,*.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 examplesdocs/LIB_REFERENCE.md— analysis of available standard libraries (../lib)docs/TASKS.md— prioritized improvement backlog