From 3cadcce32af4ff24787323a6fb2243b8b371b9b4 Mon Sep 17 00:00:00 2001 From: Jacob Schmidt Date: Sat, 16 May 2026 17:05:13 -0500 Subject: [PATCH] feat: Add ICOM usage guide and update related documentation --- arma/server/docs/README.md | 1 + arma/server/docs/api-reference.md | 2 + bin/icom/README.md | 4 +- docs/ICOM_USAGE_GUIDE.md | 195 ++++++++++++++++++ docs/MODULE_REFERENCE.md | 2 +- docs/README.md | 1 + .../1.getting-started/2.module-reference.md | 2 +- docus/content/2.server-extension/0.index.md | 1 + .../2.server-extension/1.api-reference.md | 2 + docus/content/2.server-extension/4.icom.md | 194 +++++++++++++++++ docus/content/index.md | 12 ++ examples/icom_usage.sqf | 9 - tools/sync-docus-docs.mjs | 16 ++ 13 files changed, 428 insertions(+), 13 deletions(-) create mode 100644 docs/ICOM_USAGE_GUIDE.md create mode 100644 docus/content/2.server-extension/4.icom.md diff --git a/arma/server/docs/README.md b/arma/server/docs/README.md index 761e9bc..ea53226 100644 --- a/arma/server/docs/README.md +++ b/arma/server/docs/README.md @@ -40,4 +40,5 @@ For install links and Forge-specific setup steps, see - [API Reference](./api-reference.md) - [Usage Examples](./usage-examples.md) +- [ICOM Usage Guide](../../../docs/ICOM_USAGE_GUIDE.md) - [Framework Module Guides](../../../docs/README.md) diff --git a/arma/server/docs/api-reference.md b/arma/server/docs/api-reference.md index 8cdd8c2..4d3b515 100644 --- a/arma/server/docs/api-reference.md +++ b/arma/server/docs/api-reference.md @@ -27,6 +27,7 @@ Game systems should call the domain APIs instead of raw database operations: - `store:*` - `task:*` - `cad:*` +- `icom:*` - `owned:garage:*` - `owned:locker:*` - `transport:*` @@ -47,3 +48,4 @@ needed by `forge_server_addons_extension_fnc_extCall`. - [Phone](../../../docs/PHONE_USAGE_GUIDE.md) - [Store](../../../docs/STORE_USAGE_GUIDE.md) - [Task](../../../docs/TASK_USAGE_GUIDE.md) +- [ICOM](../../../docs/ICOM_USAGE_GUIDE.md) diff --git a/bin/icom/README.md b/bin/icom/README.md index 7adb73b..df1fa5f 100644 --- a/bin/icom/README.md +++ b/bin/icom/README.md @@ -212,7 +212,7 @@ client.listen_for_events(|msg| { The Forge server extension includes full ICOM integration: -1. **Initialization**: Connects to ICOM on extension startup (or manually via `icom:connect`) +1. **Initialization**: Connect with `icom:connect` after the ICOM hub is running. 2. **Event Listener**: Spawns background task to receive events continuously 3. **Callback System**: Forwards events to Arma via CBA event handlers 4. **Extension Commands**: Provides SQF commands to send/receive events @@ -221,7 +221,7 @@ The Forge server extension includes full ICOM integration: - The extension uses `try_read()` to avoid deadlocks when accessing context from async tasks - Broadcast events are **not** sent back to the originating server -- Connection can be initiated manually if automatic startup connection fails +- Connection is initiated through the `icom:connect` extension command. ### SQF Usage diff --git a/docs/ICOM_USAGE_GUIDE.md b/docs/ICOM_USAGE_GUIDE.md new file mode 100644 index 0000000..36a3bb0 --- /dev/null +++ b/docs/ICOM_USAGE_GUIDE.md @@ -0,0 +1,195 @@ +# ICOM Usage Guide + +ICOM is the Forge inter-server communication helper. It lets multiple Arma 3 +servers exchange generic JSON events through a central TCP hub instead of +connecting directly to each other. + +## Runtime Shape + +```text +Arma server SQF + -> forge_server extension icom:* command + -> ICOM client inside the extension + -> forge-icom TCP hub + -> target server extension + -> forge_icom_event CBA server event +``` + +The ICOM hub lives in `bin/icom`. The Arma server extension integrates with it +through `arma/server/extension/src/icom.rs`. + +## Components + +| Component | Path | Role | +| --- | --- | --- | +| ICOM hub binary | `bin/icom` | Standalone TCP router for connected servers. | +| ICOM client library | `bin/icom/src/client.rs` | Rust client used by the Forge server extension and examples. | +| Extension command group | `arma/server/extension/src/icom.rs` | Exposes `icom:*` commands to SQF and forwards inbound events to Arma. | +| SQF callback bridge | `arma/server/addons/main/XEH_preInit.sqf` | Receives extension callbacks and re-emits `forge_icom_event` through CBA. | + +## Build and Run the Hub + +Build the release binary: + +```powershell +cargo build --release -p forge-icom +``` + +Run it during development: + +```powershell +cargo run -p forge-icom +``` + +The default bind address is `0.0.0.0:9090`. + +## Hub Configuration + +Copy `bin/icom/config.example.toml` to `config.toml` beside the `forge-icom` +executable or into the working directory used to launch it. + +```toml +[server] +host = "0.0.0.0" +port = 9090 +``` + +Use `127.0.0.1` for same-machine testing. Use `0.0.0.0` when remote Arma +servers need to connect, and secure the port at the firewall or host network +layer. + +## Extension Commands + +ICOM commands are exposed through the `icom` command group in `forge_server`. + +| Command | Arguments | Returns | +| --- | --- | --- | +| `icom:connect` | `address`, `server_id` | `Connection initiated` or `ERROR: Already connected`. | +| `icom:send_event` | `target_server`, `event_name`, `data_json` | `OK` or `ERROR: `. | +| `icom:broadcast` | `event_name`, `data_json` | `OK` or `ERROR: `. | + +The current extension connects when `icom:connect` is called. Start the ICOM hub +first, then connect each Arma server with a unique `server_id`. + +```sqf +private _result = "forge_server" callExtension [ + "icom:connect", + ["127.0.0.1:9090", "server_1"] +]; +diag_log format ["[ICOM] Connect result: %1", _result select 0]; +``` + +## Send an Event + +Send a targeted event to one connected server: + +```sqf +private _data = createHashMapFromArray [ + ["coords", [1234, 5678, 0]], + ["supplies", ["ammo_box", "medical_supplies"]] +]; + +"forge_server" callExtension [ + "icom:send_event", + ["server_2", "supply_drop", toJSON _data] +]; +``` + +Broadcast to every connected server except the sender: + +```sqf +private _alert = createHashMapFromArray [ + ["message", "Server restart in 5 minutes"], + ["severity", "warning"] +]; + +"forge_server" callExtension [ + "icom:broadcast", + ["global_alert", toJSON _alert] +]; +``` + +## Receive Events + +Inbound ICOM events are forwarded to SQF as the CBA server event +`forge_icom_event`. + +```sqf +["forge_icom_event", { + params ["_eventName", "_data"]; + + switch (_eventName) do { + case "supply_drop": { + private _coords = _data getOrDefault ["coords", []]; + private _supplies = _data getOrDefault ["supplies", []]; + diag_log format ["[ICOM] Supply drop at %1: %2", _coords, _supplies]; + }; + case "global_alert": { + private _message = _data getOrDefault ["message", ""]; + if (_message isNotEqualTo "") then { + [_message] remoteExec ["hint", 0]; + }; + }; + default { + diag_log format ["[ICOM] Unhandled event: %1 | %2", _eventName, _data]; + }; + }; +}] call CBA_fnc_addEventHandler; +``` + +## Message Protocol + +The hub uses newline-delimited JSON. The first message from each client is a +registration payload: + +```json +{ + "type": "register", + "server_id": "server_1" +} +``` + +Targeted events use `type: "event"`: + +```json +{ + "type": "event", + "target_server": "server_2", + "event_name": "supply_drop", + "data": { + "coords": [1234, 5678, 0] + } +} +``` + +Broadcasts use `type: "broadcast"` and are routed to all connected servers +except the sender. + +## Operational Notes + +- Server IDs must be unique. If the same ID reconnects, the hub replaces the old + connection. +- Event names are mission/application contracts. ICOM only routes them; it does + not validate gameplay meaning. +- Always send valid JSON in the `data_json` argument. +- `icom:send_event` and `icom:broadcast` return quickly after scheduling async + work in the extension. Check extension and ICOM hub logs for delivery errors. +- Keep event payloads small and stable. Use IDs or compact data where possible. + +## Testing + +Start the hub: + +```powershell +cargo run -p forge-icom +``` + +Run example clients in separate terminals: + +```powershell +cargo run -p forge-icom --example server_1_client +cargo run -p forge-icom --example server_2_client +``` + +For Arma testing, start the hub, connect the server with `icom:connect`, register +a `forge_icom_event` handler, then send an event from another connected server. diff --git a/docs/MODULE_REFERENCE.md b/docs/MODULE_REFERENCE.md index 6144d1f..b673b08 100644 --- a/docs/MODULE_REFERENCE.md +++ b/docs/MODULE_REFERENCE.md @@ -203,7 +203,7 @@ See [Owned Storage Usage Guide](./OWNED_STORAGE_USAGE_GUIDE.md) for examples. | Command Group | Purpose | | --- | --- | | `store:checkout` | Run store checkout behavior. | -| `icom:connect`, `icom:broadcast`, `icom:send_event` | ICom connection and event forwarding. | +| `icom:connect`, `icom:broadcast`, `icom:send_event` | ICOM connection and event forwarding. See [ICOM Usage Guide](./ICOM_USAGE_GUIDE.md). | | `terrain:exportSVG` | Export terrain data as SVG. | | `transport:invoke`, `transport:invoke_stored` | Invoke commands through transport. | | `transport:request:append`, `transport:request:clear` | Manage stored request chunks. | diff --git a/docs/README.md b/docs/README.md index 84419e8..b1ed418 100644 --- a/docs/README.md +++ b/docs/README.md @@ -23,6 +23,7 @@ collects framework-level documentation for those pieces. - [CAD Usage Guide](./CAD_USAGE_GUIDE.md) - [Economy Usage Guide](./ECONOMY_USAGE_GUIDE.md) - [Garage Usage Guide](./GARAGE_USAGE_GUIDE.md) +- [ICOM Usage Guide](./ICOM_USAGE_GUIDE.md) - [Locker Usage Guide](./LOCKER_USAGE_GUIDE.md) - [Organization Usage Guide](./ORG_USAGE_GUIDE.md) - [Owned Storage Usage Guide](./OWNED_STORAGE_USAGE_GUIDE.md) diff --git a/docus/content/1.getting-started/2.module-reference.md b/docus/content/1.getting-started/2.module-reference.md index 54b9fbf..2143339 100644 --- a/docus/content/1.getting-started/2.module-reference.md +++ b/docus/content/1.getting-started/2.module-reference.md @@ -204,7 +204,7 @@ See [Owned Storage Usage Guide](/server-modules/owned-storage) for examples. | Command Group | Purpose | | --- | --- | | `store:checkout` | Run store checkout behavior. | -| `icom:connect`, `icom:broadcast`, `icom:send_event` | ICom connection and event forwarding. | +| `icom:connect`, `icom:broadcast`, `icom:send_event` | ICOM connection and event forwarding. See [ICOM Usage Guide](/server-extension/icom). | | `terrain:exportSVG` | Export terrain data as SVG. | | `transport:invoke`, `transport:invoke_stored` | Invoke commands through transport. | | `transport:request:append`, `transport:request:clear` | Manage stored request chunks. | diff --git a/docus/content/2.server-extension/0.index.md b/docus/content/2.server-extension/0.index.md index 2357d1c..4413988 100644 --- a/docus/content/2.server-extension/0.index.md +++ b/docus/content/2.server-extension/0.index.md @@ -39,4 +39,5 @@ For install links and Forge-specific setup steps, see - [API Reference](/server-extension/api-reference) - [Usage Examples](/server-extension/usage-examples) +- [ICOM Usage Guide](/server-extension/icom) - [Framework Module Guides](/getting-started) diff --git a/docus/content/2.server-extension/1.api-reference.md b/docus/content/2.server-extension/1.api-reference.md index 7a67993..261121a 100644 --- a/docus/content/2.server-extension/1.api-reference.md +++ b/docus/content/2.server-extension/1.api-reference.md @@ -26,6 +26,7 @@ Game systems should call the domain APIs instead of raw database operations: - `store:*` - `task:*` - `cad:*` +- `icom:*` - `owned:garage:*` - `owned:locker:*` - `transport:*` @@ -46,3 +47,4 @@ needed by `forge_server_addons_extension_fnc_extCall`. - [Phone](/server-modules/phone) - [Store](/server-modules/store) - [Task](/server-modules/task) +- [ICOM](/server-extension/icom) diff --git a/docus/content/2.server-extension/4.icom.md b/docus/content/2.server-extension/4.icom.md new file mode 100644 index 0000000..778e75d --- /dev/null +++ b/docus/content/2.server-extension/4.icom.md @@ -0,0 +1,194 @@ +--- +title: "ICOM Usage Guide" +description: "ICOM is the Forge inter-server communication helper. It lets multiple Arma 3 servers exchange generic JSON events through a central TCP hub instead of connecting directly to each other." +--- + +## Runtime Shape + +```text +Arma server SQF + -> forge_server extension icom:* command + -> ICOM client inside the extension + -> forge-icom TCP hub + -> target server extension + -> forge_icom_event CBA server event +``` + +The ICOM hub lives in `bin/icom`. The Arma server extension integrates with it +through `arma/server/extension/src/icom.rs`. + +## Components + +| Component | Path | Role | +| --- | --- | --- | +| ICOM hub binary | `bin/icom` | Standalone TCP router for connected servers. | +| ICOM client library | `bin/icom/src/client.rs` | Rust client used by the Forge server extension and examples. | +| Extension command group | `arma/server/extension/src/icom.rs` | Exposes `icom:*` commands to SQF and forwards inbound events to Arma. | +| SQF callback bridge | `arma/server/addons/main/XEH_preInit.sqf` | Receives extension callbacks and re-emits `forge_icom_event` through CBA. | + +## Build and Run the Hub + +Build the release binary: + +```powershell +cargo build --release -p forge-icom +``` + +Run it during development: + +```powershell +cargo run -p forge-icom +``` + +The default bind address is `0.0.0.0:9090`. + +## Hub Configuration + +Copy `bin/icom/config.example.toml` to `config.toml` beside the `forge-icom` +executable or into the working directory used to launch it. + +```toml +[server] +host = "0.0.0.0" +port = 9090 +``` + +Use `127.0.0.1` for same-machine testing. Use `0.0.0.0` when remote Arma +servers need to connect, and secure the port at the firewall or host network +layer. + +## Extension Commands + +ICOM commands are exposed through the `icom` command group in `forge_server`. + +| Command | Arguments | Returns | +| --- | --- | --- | +| `icom:connect` | `address`, `server_id` | `Connection initiated` or `ERROR: Already connected`. | +| `icom:send_event` | `target_server`, `event_name`, `data_json` | `OK` or `ERROR: `. | +| `icom:broadcast` | `event_name`, `data_json` | `OK` or `ERROR: `. | + +The current extension connects when `icom:connect` is called. Start the ICOM hub +first, then connect each Arma server with a unique `server_id`. + +```sqf +private _result = "forge_server" callExtension [ + "icom:connect", + ["127.0.0.1:9090", "server_1"] +]; +diag_log format ["[ICOM] Connect result: %1", _result select 0]; +``` + +## Send an Event + +Send a targeted event to one connected server: + +```sqf +private _data = createHashMapFromArray [ + ["coords", [1234, 5678, 0]], + ["supplies", ["ammo_box", "medical_supplies"]] +]; + +"forge_server" callExtension [ + "icom:send_event", + ["server_2", "supply_drop", toJSON _data] +]; +``` + +Broadcast to every connected server except the sender: + +```sqf +private _alert = createHashMapFromArray [ + ["message", "Server restart in 5 minutes"], + ["severity", "warning"] +]; + +"forge_server" callExtension [ + "icom:broadcast", + ["global_alert", toJSON _alert] +]; +``` + +## Receive Events + +Inbound ICOM events are forwarded to SQF as the CBA server event +`forge_icom_event`. + +```sqf +["forge_icom_event", { + params ["_eventName", "_data"]; + + switch (_eventName) do { + case "supply_drop": { + private _coords = _data getOrDefault ["coords", []]; + private _supplies = _data getOrDefault ["supplies", []]; + diag_log format ["[ICOM] Supply drop at %1: %2", _coords, _supplies]; + }; + case "global_alert": { + private _message = _data getOrDefault ["message", ""]; + if (_message isNotEqualTo "") then { + [_message] remoteExec ["hint", 0]; + }; + }; + default { + diag_log format ["[ICOM] Unhandled event: %1 | %2", _eventName, _data]; + }; + }; +}] call CBA_fnc_addEventHandler; +``` + +## Message Protocol + +The hub uses newline-delimited JSON. The first message from each client is a +registration payload: + +```json +{ + "type": "register", + "server_id": "server_1" +} +``` + +Targeted events use `type: "event"`: + +```json +{ + "type": "event", + "target_server": "server_2", + "event_name": "supply_drop", + "data": { + "coords": [1234, 5678, 0] + } +} +``` + +Broadcasts use `type: "broadcast"` and are routed to all connected servers +except the sender. + +## Operational Notes + +- Server IDs must be unique. If the same ID reconnects, the hub replaces the old + connection. +- Event names are mission/application contracts. ICOM only routes them; it does + not validate gameplay meaning. +- Always send valid JSON in the `data_json` argument. +- `icom:send_event` and `icom:broadcast` return quickly after scheduling async + work in the extension. Check extension and ICOM hub logs for delivery errors. +- Keep event payloads small and stable. Use IDs or compact data where possible. + +## Testing + +Start the hub: + +```powershell +cargo run -p forge-icom +``` + +Run example clients in separate terminals: + +```powershell +cargo run -p forge-icom --example server_1_client +cargo run -p forge-icom --example server_2_client +``` + +For Arma testing, start the hub, connect the server with `icom:connect`, register +a `forge_icom_event` handler, then send an event from another connected server. diff --git a/docus/content/index.md b/docus/content/index.md index 123ddf7..41b53ee 100644 --- a/docus/content/index.md +++ b/docus/content/index.md @@ -146,6 +146,18 @@ Documentation Areas Extension architecture, command surface, and SQF usage examples. ::: + :::u-page-feature + --- + icon: i-lucide-network + to: /server-extension/icom + --- + #title + ICOM [Events]{.text-primary} + + #description + Inter-server event routing through the Forge ICOM hub and extension commands. + ::: + :::u-page-feature --- icon: i-lucide-layers-3 diff --git a/examples/icom_usage.sqf b/examples/icom_usage.sqf index e38627a..2c8744f 100644 --- a/examples/icom_usage.sqf +++ b/examples/icom_usage.sqf @@ -118,15 +118,6 @@ private _playerJoinData = createHashMapFromArray [ "forge_server" callExtension ["icom:broadcast", ["player_join", (toJSON _playerJoinData)]]; -// ============================================================================ -// STEP 3: Test the connection -// ============================================================================ - -// Test if ICOM is working -"forge_server" callExtension ["icom:test_callback", []]; -// Should trigger forge_icom_event with a handshake event - - // ============================================================================ // TIPS AND BEST PRACTICES // ============================================================================ diff --git a/tools/sync-docus-docs.mjs b/tools/sync-docus-docs.mjs index 96389ee..aa9a6b4 100644 --- a/tools/sync-docus-docs.mjs +++ b/tools/sync-docus-docs.mjs @@ -39,6 +39,10 @@ const generatedPages = [ source: 'arma/server/addons/common/README.md', target: '2.server-extension/3.common.md' }, + { + source: 'docs/ICOM_USAGE_GUIDE.md', + target: '2.server-extension/4.icom.md' + }, { source: 'docs/ACTOR_USAGE_GUIDE.md', target: '3.server-modules/1.actor.md' @@ -293,6 +297,18 @@ Documentation Areas Extension architecture, command surface, and SQF usage examples. ::: + :::u-page-feature + --- + icon: i-lucide-network + to: /server-extension/icom + --- + #title + ICOM [Events]{.text-primary} + + #description + Inter-server event routing through the Forge ICOM hub and extension commands. + ::: + :::u-page-feature --- icon: i-lucide-layers-3