Skip to content

Commit 89886e5

Browse files
committed
wip
1 parent d8cc6f2 commit 89886e5

27 files changed

+2112
-81
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@ env/
99

1010
# don't track local (dev) file
1111
*.local
12+
*.code-workspace

development_notes.md

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
# Development Notes
2+
3+
This document outlines the architecture and development process for integrating native Rust code with the C++ engine.
4+
5+
## Architecture
6+
7+
The system consists of two main parts:
8+
9+
1. **C++ Engine (`spring-bar`):** The core engine written in C++.
10+
2. **Native Rust Module (`SBC.sdd/native`):** A Rust library that extends the engine's functionality.
11+
12+
Communication between these two parts is achieved through a native interface.
13+
14+
### Native Interface
15+
16+
The bridge between C++ and Rust is defined by the following key files:
17+
18+
- **C++ Side:**
19+
- `rts/Game/Rust/NativeInterface.h`: Defines the interface structs (`NativeInterfaceBridge` and `NativeInterface`) that Rust will interact with.
20+
- `rts/Game/Rust/RustSystem.h` / `rts/Game/Rust/RustSystem.cpp`: Implements the C++ side of the bridge, providing the actual functionality that the interface exposes.
21+
22+
- **Rust Side:**
23+
- `native/src/native_interface.rs`: Defines the Rust representation of the native interface, allowing Rust code to call the C++ functions.
24+
25+
## Development Process
26+
27+
To add a new function to the native interface (e.g., `MyNewFunction`):
28+
29+
1. **`NativeInterface.h`:**
30+
- Add the virtual function declaration to the `NativeInterfaceBridge` struct:
31+
```cpp
32+
virtual void MyNewFunction(int arg1, float arg2) = 0;
33+
```
34+
- Add the corresponding function pointer type and member to the `NativeInterface` struct:
35+
```cpp
36+
using MyNewFunction = void(*)(NativeInterface* ptr, int arg1, float arg2);
37+
MyNewFunction f_MyNewFunction;
38+
```
39+
40+
2. **`RustSystem.h`:**
41+
- Add the public override declaration for the new function in the `RustSystem` class:
42+
```cpp
43+
void MyNewFunction(int arg1, float arg2) override;
44+
```
45+
46+
3. **`RustSystem.cpp`:**
47+
- Implement the function:
48+
```cpp
49+
void RustSystem::MyNewFunction(int arg1, float arg2) {
50+
// Implementation here
51+
}
52+
```
53+
- Add the function to the `m_NativeInterface` struct initialization:
54+
```cpp
55+
.f_MyNewFunction = [](NativeInterface* ptr, int arg1, float arg2) {
56+
return ptr->bridge->MyNewFunction(arg1, arg2);
57+
},
58+
```
59+
60+
4. **`native_interface.rs`:**
61+
- Add the function signature to the `NativeInterfaceImpl` struct:
62+
```rust
63+
f_MyNewFunction: extern "C" fn(ptr: *const NativeInterfaceImpl, arg1: i32, arg2: f32),
64+
```
65+
- Add the function to the `NativeInterface` trait:
66+
```rust
67+
fn MyNewFunction(&self, arg1: i32, arg2: f32);
68+
```
69+
- Implement the function for the trait:
70+
```rust
71+
fn MyNewFunction(&self, arg1: i32, arg2: f32) {
72+
(self.f_MyNewFunction)(self as *const NativeInterfaceImpl, arg1, arg2)
73+
}
74+
```
75+
76+
5. **Usage in Rust:**
77+
- You can now call `Spring.MyNewFunction(arg1, arg2)` from anywhere in the Rust code.
78+
79+
## Build Process
80+
81+
- **C++ (`spring-bar`):** Build using the provided docker script: `./docker-build-v2/build.sh linux`.
82+
- **Rust (`SBC.sdd/native`):** Build using `cargo build` in the `native` directory.
83+
84+
## Porting from Lua
85+
86+
Existing Lua implementations in `rts/Lua` serve as a reference when porting functionality to the native Rust module. The goal is to replicate the logic from the Lua files in Rust, using the newly created native interface functions.

dist_cfg/config.json

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@
166166

167167
{
168168
"package": {
169-
"id": "dev-bar-engine",
169+
"id": "dev-bar",
170170
"display": "Development (BAR Engine)"
171171
},
172172
"downloads": {
@@ -188,6 +188,30 @@
188188
}
189189
},
190190

191+
{
192+
"package": {
193+
"id": "dev-bar-dev",
194+
"display": "Development (Dev BAR Engine)"
195+
},
196+
"downloads": {
197+
"engines" : [ "local-build-v2" ]
198+
},
199+
"auto_start" : true,
200+
"no_downloads" : true,
201+
"load_dev_exts": false,
202+
"launch": {
203+
"game": "SpringBoard Core $VERSION",
204+
"map": "sb_initial_blank_10x8",
205+
"map_options": {
206+
"new_map_x": 10,
207+
"new_map_y": 8
208+
},
209+
"game_options": {
210+
"MapSeed": 42
211+
}
212+
}
213+
},
214+
191215
{
192216
"package": {
193217
"id": "asset-download",

doc/native_interface/abi.md

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
# Native Interface ABI Strategy (Revised)
2+
3+
This document describes how the C++ engine and native modules negotiate compatibility, expose new features safely, and validate behavior as the Lua bridge is ported.
4+
5+
## Semantic Versioning
6+
7+
The native interface uses semantic versioning applied to the entire API surface:
8+
9+
- **MAJOR** – incremented when we intentionally break compatibility (removing fields, changing semantics). Modules built against a different major version are rejected.
10+
- **MINOR** – incremented when we add functionality in a backward-compatible way (new functions, new struct fields appended). Older modules continue to run but cannot see the new entries.
11+
- **PATCH** – bug fixes or comment-only changes. No behavior change.
12+
13+
Shared header constants:
14+
15+
```c
16+
#define SPRING_NATIVE_ABI_MAJOR 1
17+
#define SPRING_NATIVE_ABI_MINOR 0
18+
#define SPRING_NATIVE_ABI_PATCH 0
19+
```
20+
21+
Rust bindings (via `build.rs` or generated code) read the same numbers so both sides agree.
22+
23+
## Initialization Handshake
24+
25+
Compatibility is enforced in a single initialization call. The engine exports:
26+
27+
```c
28+
struct NativeInterface;
29+
30+
struct NativeInitParams {
31+
uint16_t abi_major;
32+
uint16_t abi_minor;
33+
uint16_t abi_patch;
34+
uint32_t engine_commits_number; // optional, parsed from SpringVersion::GetCommits()
35+
};
36+
37+
typedef bool (*NativeModuleInitFn)(const NativeInterface* iface,
38+
const NativeInitParams* params,
39+
struct NativeModuleInfo* out_info);
40+
```
41+
42+
Example engine loader:
43+
44+
```c
45+
bool EngineLoadModule(NativeModuleInitFn initFn) {
46+
NativeInitParams params;
47+
params.abi_major = SPRING_NATIVE_ABI_MAJOR;
48+
params.abi_minor = SPRING_NATIVE_ABI_MINOR;
49+
params.abi_patch = SPRING_NATIVE_ABI_PATCH;
50+
params.engine_commits_number = std::atoi(SpringVersion::GetCommits().c_str());
51+
52+
NativeModuleInfo info = {};
53+
if (!initFn(&nativeInterface, &params, &info)) {
54+
return false; // module rejected the engine
55+
}
56+
57+
if (info.abi_major != SPRING_NATIVE_ABI_MAJOR)
58+
return false;
59+
60+
if (info.abi_minor > SPRING_NATIVE_ABI_MINOR)
61+
return false;
62+
63+
RegisterModule(info);
64+
return true;
65+
}
66+
```
67+
- Requires `<cstdlib>` for `std::atoi`.
68+
69+
The module reports its details through `NativeModuleInfo`:
70+
71+
```c
72+
struct NativeModuleInfo {
73+
const char* module_name; // optional diagnostics
74+
uint16_t abi_major;
75+
uint16_t abi_minor;
76+
uint16_t abi_patch;
77+
};
78+
```
79+
80+
Once the handshake succeeds both sides know the shared MAJOR/MINOR numbers and can make decisions about optional features.
81+
82+
## Function Tables
83+
84+
Each domain (SyncedCtrl, MetalMap, etc.) exposes a struct of function pointers inside `NativeInterface`.
85+
86+
Rules:
87+
88+
1. New functions are appended to the end of the struct when MINOR increases.
89+
2. Existing functions keep their signature and semantics until MAJOR changes.
90+
3. The engine always zero-initialises the function table; missing functions appear as `nullptr`.
91+
4. Modules must null-check optional entries if they require features added after their own `abi_minor`.
92+
93+
Example skeleton:
94+
95+
```c
96+
struct NativeSyncedCtrl {
97+
AddHeightMapFn add_height_map; // since 1.0
98+
SetHeightMapFn set_height_map; // since 1.0
99+
SetHeightMapFuncFn set_height_map_fn; // since 1.0
100+
101+
LevelHeightMapFn level_height_map; // added in 1.1 (nullptr on 1.0)
102+
AdjustHeightMapFn adjust_height_map; // added in 1.1
103+
};
104+
```
105+
106+
Usage:
107+
108+
```c
109+
if (moduleInfo.abi_minor >= 1 && iface->synced_ctrl.level_height_map != nullptr) {
110+
iface->synced_ctrl.level_height_map(&request);
111+
}
112+
```
113+
114+
Because the engine knows the module’s `abi_minor`, it can also avoid calling back into functions the module never provided.
115+
116+
## Struct Layouts and Arrays
117+
118+
Data exchanged across the boundary stays POD:
119+
120+
- Fixed-width integers (`uint32_t`, `int32_t`, etc.) and `float`/`double`.
121+
- Pointers combined with explicit lengths for arrays:
122+
```c
123+
struct FloatArrayView {
124+
const float* values;
125+
uint32_t length;
126+
};
127+
```
128+
- Null-terminated UTF-8 strings where needed (`const char*`).
129+
130+
When a struct grows, new fields are appended. The engine only writes fields that exist for the module’s negotiated MINOR version. Array shapes and helper structs live in the specification directory (`spec/README.md`).
131+
132+
## Call-In / Call-Out Catalogue
133+
134+
- **Call-ins (module → engine)**: the function tables described above. Modules call what they need, guarding `nullptr` for optional entries.
135+
- **Call-outs (engine → module)**: planned via callback tables returned in `NativeModuleInfo` when needed. They will follow the same semver rules (append-only while MINOR increases).
136+
137+
## Hot Reload Workflow
138+
139+
1. Engine unloads the previous shared library and loads the new one.
140+
2. `NativeModuleInitFn` runs again; if the module rejects the current ABI, reload aborts.
141+
3. The module updates its cached pointers to the `NativeInterface` tables.
142+
143+
144+
## Open Items
145+
146+
- Decide on the machine-readable format (YAML/TOML/JSON) that will drive code generation.
147+
- Specify the callback table format once engine-driven call-outs are required.
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# ConstCMD API
2+
3+
## Legacy Lua Behaviour
4+
`LuaConstCMD.cpp` populates the `CMD` table with:
5+
6+
- Option bit flags (`CMD.OPT_*`).
7+
- State constants (move, fire, wait).
8+
- A bidirectional map between command names and numeric IDs (`CMD.ATTACK == 20`, `CMD[20] == "ATTACK"`).
9+
- Backwards-compatibility aliases (e.g. `CMD.DGUN``CMD.MANUALFIRE`).
10+
11+
Lua expects these values to be immutable and available in both synced and unsynced environments.
12+
13+
## Native Interface Design
14+
Expose the same data through a read-only C ABI so native modules can look up command identifiers without going through Lua.
15+
16+
```c
17+
#ifdef __cplusplus
18+
extern "C" {
19+
#endif
20+
21+
struct NativeCmdLabeledValue {
22+
const char* name; // UTF-8, null-terminated
23+
int32_t value; // option bit, state code, wait code, etc.
24+
};
25+
26+
struct NativeCmdCommandEntry {
27+
const char* name; // e.g. "ATTACK"
28+
int32_t id; // e.g. 20
29+
};
30+
31+
struct NativeCmdAlias {
32+
const char* alias; // e.g. "DGUN"
33+
int32_t id; // maps to command id
34+
};
35+
36+
struct NativeConstCMD {
37+
const struct NativeCmdLabeledValue* option_flags; // OPT_* entries
38+
size_t option_flags_len;
39+
40+
const struct NativeCmdLabeledValue* move_states; // MOVESTATE_*
41+
size_t move_states_len;
42+
43+
const struct NativeCmdLabeledValue* fire_states; // FIRESTATE_*
44+
size_t fire_states_len;
45+
46+
const struct NativeCmdLabeledValue* wait_codes; // WAITCODE_*
47+
size_t wait_codes_len;
48+
49+
const struct NativeCmdCommandEntry* commands; // CMD.* identifiers
50+
size_t commands_len;
51+
52+
const struct NativeCmdAlias* aliases; // compatibility aliases
53+
size_t aliases_len;
54+
55+
// Helper callbacks provided by the engine (fast lookups over generated tables)
56+
int32_t (*id_from_name)(const struct NativeConstCMD* self, const char* name);
57+
const char* (*name_from_id)(const struct NativeConstCMD* self, int32_t id);
58+
};
59+
60+
// Returns a pointer to an immutable singleton owned by the engine.
61+
// The pointer remains valid for the lifetime of the process.
62+
typedef const struct NativeConstCMD* (*GetConstCMDFn)(void);
63+
64+
#ifdef __cplusplus
65+
} // extern "C"
66+
#endif
67+
```
68+
69+
### Engine Responsibilities
70+
- Populate the arrays at initialization using the same source data as `LuaConstCMD.cpp` and keep them sorted by name for deterministic lookups.
71+
- Implement `id_from_name` / `name_from_id` as pure, thread-safe helpers (e.g. binary search over the sorted arrays).
72+
- Expose `GetConstCMDFn` from the `NativeInterface` (e.g. `iface->const_tables.get_cmd`).
73+
74+
### Consumer Guidance
75+
- Treat all pointers as read-only; copy strings/values if mutability is required.
76+
- Cache the returned pointer if desired—no hot-reload updates are expected for constants.
77+
- Use the helpers for lookups rather than scanning arrays manually.

0 commit comments

Comments
 (0)