paavohtl Claude Opus 4.6 (1M context) commited on
Commit
df6d1b6
Β·
1 Parent(s): 24cf333

feat: add CTaskVtable, broadcast_message, and retype children_data

Browse files

Add base CTaskVtable (8 slots, 0x669F8C) via #[vtable] macro with
write_replay_state, free, handle_message, and process_frame.

Add Task::broadcast_message() β€” pure Rust port of CTask::HandleMessage
(0x562F30) that iterates the sparse children array and dispatches via
typed CTaskVtable. Currently unused (callers use original WA function
pending investigation of a subtle iteration bug).

Retype CTask.children_data from *mut u8 to *mut *mut CTask for type
safety.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

crates/openwa-core/src/fixed.rs CHANGED
@@ -1,4 +1,5 @@
1
  use core::ops::{Add, Mul, Neg, Sub};
 
2
 
3
  /// 16.16 fixed-point number used throughout WA for coordinates and velocities.
4
  ///
@@ -90,3 +91,17 @@ impl core::fmt::Display for Fixed {
90
  write!(f, "{:.4}", self.to_f32())
91
  }
92
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  use core::ops::{Add, Mul, Neg, Sub};
2
+ use std::ops::{AddAssign, SubAssign};
3
 
4
  /// 16.16 fixed-point number used throughout WA for coordinates and velocities.
5
  ///
 
91
  write!(f, "{:.4}", self.to_f32())
92
  }
93
  }
94
+
95
+ impl AddAssign for Fixed {
96
+ #[inline]
97
+ fn add_assign(&mut self, rhs: Self) {
98
+ self.0 += rhs.0;
99
+ }
100
+ }
101
+
102
+ impl SubAssign for Fixed {
103
+ #[inline]
104
+ fn sub_assign(&mut self, rhs: Self) {
105
+ self.0 -= rhs.0;
106
+ }
107
+ }
crates/openwa-core/src/task/base.rs CHANGED
@@ -87,21 +87,32 @@ crate::define_addresses! {
87
  }
88
  }
89
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
90
  /// Base task class in WA's entity hierarchy.
91
  ///
92
  /// All game objects inherit from CTask. Tasks form a tree via parent/children
93
  /// pointers and communicate through the TaskMessage system.
94
  ///
95
  /// Source: wkJellyWorm CTask.h, Ghidra decompilation of 0x5625A0 + 0x562520
96
- ///
97
- /// Vtable at 0x669F8C (8 methods):
98
- /// 0x00: 0x562710 vtable0 (init?)
99
- /// 0x04: 0x562620 Free
100
- /// 0x08: 0x562F30 HandleMessage
101
- /// 0x0C: 0x5613D0 unknown
102
- /// 0x10: 0x5613D0 unknown (same as 0x0C)
103
- /// 0x14: 0x562FA0 unknown
104
- /// 0x18: 0x563000 unknown
105
  /// 0x1C: 0x563210 ProcessFrame
106
  #[derive(FieldRegistry)]
107
  #[repr(C)]
@@ -122,7 +133,8 @@ pub struct CTask<V: Vtable = *const core::ffi::c_void> {
122
  pub children_watermark: u32,
123
  /// 0x14: Pointer to children data array (sparse, allocated 0x60 bytes initially,
124
  /// reallocated to `children_capacity * 8 + 0x20` bytes on overflow).
125
- pub children_data: *mut u8,
 
126
  /// 0x18: Children hash list pointer (set to 0 in constructor)
127
  pub children_hash: *mut u8,
128
  /// 0x1C: Unknown (set to 0 by parent-linking helper FUN_00562520)
@@ -186,6 +198,61 @@ pub unsafe trait Task {
186
  fn ddgame(&self) -> *mut DDGame {
187
  self.task().ddgame
188
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
189
  }
190
 
191
  // Blanket impl for any CTask<V>
 
87
  }
88
  }
89
 
90
+ /// CTask base vtable β€” 8 slots shared by all task types.
91
+ ///
92
+ /// Every CTask subclass vtable starts with these 8 slots. Subclasses override
93
+ /// individual slots and extend with additional class-specific methods.
94
+ #[openwa_core::vtable(size = 8, va = 0x0066_9F8C, class = "CTask")]
95
+ pub struct CTaskVtable {
96
+ /// WriteReplayState β€” serializes task state to replay stream.
97
+ #[slot(0)]
98
+ pub write_replay_state: fn(this: *mut CTask, stream: *mut u8),
99
+ /// Free β€” destructor. Frees the task and optionally its allocation.
100
+ #[slot(1)]
101
+ pub free: fn(this: *mut CTask, flags: u8) -> *mut CTask,
102
+ /// HandleMessage β€” broadcasts message to all children (base implementation).
103
+ #[slot(2)]
104
+ pub handle_message: fn(this: *mut CTask, sender: *mut CTask, msg_type: u32, size: u32, data: *const u8),
105
+ /// ProcessFrame β€” per-frame update. Base implementation is a no-op.
106
+ #[slot(7)]
107
+ pub process_frame: fn(this: *mut CTask, flags: u32),
108
+ }
109
+
110
  /// Base task class in WA's entity hierarchy.
111
  ///
112
  /// All game objects inherit from CTask. Tasks form a tree via parent/children
113
  /// pointers and communicate through the TaskMessage system.
114
  ///
115
  /// Source: wkJellyWorm CTask.h, Ghidra decompilation of 0x5625A0 + 0x562520
 
 
 
 
 
 
 
 
 
116
  /// 0x1C: 0x563210 ProcessFrame
117
  #[derive(FieldRegistry)]
118
  #[repr(C)]
 
133
  pub children_watermark: u32,
134
  /// 0x14: Pointer to children data array (sparse, allocated 0x60 bytes initially,
135
  /// reallocated to `children_capacity * 8 + 0x20` bytes on overflow).
136
+ /// Non-null entries are valid CTask pointers (any subclass).
137
+ pub children_data: *mut *mut CTask,
138
  /// 0x18: Children hash list pointer (set to 0 in constructor)
139
  pub children_hash: *mut u8,
140
  /// 0x1C: Unknown (set to 0 by parent-linking helper FUN_00562520)
 
198
  fn ddgame(&self) -> *mut DDGame {
199
  self.task().ddgame
200
  }
201
+
202
+ /// Broadcast a message to all children β€” pure Rust port of CTask::HandleMessage (0x562F30).
203
+ ///
204
+ /// Iterates the sparse children array (`children_data[0..children_watermark]`),
205
+ /// skips null entries, and calls each child's `HandleMessage` (vtable slot 2).
206
+ /// This is how messages propagate down the task tree.
207
+ ///
208
+ /// # Safety
209
+ /// All non-null children must be valid CTask pointers with valid vtables.
210
+ unsafe fn broadcast_message(
211
+ &self,
212
+ sender: *mut CTask,
213
+ msg_type: u32,
214
+ size: u32,
215
+ data: *const u8,
216
+ ) {
217
+ let task_ptr = self.as_task_ptr();
218
+
219
+ // Find the first non-null child
220
+ let mut i: usize = 0;
221
+ let mut child = core::ptr::null_mut::<CTask>();
222
+ loop {
223
+ let watermark = (*task_ptr).children_watermark as usize;
224
+ if i >= watermark {
225
+ break;
226
+ }
227
+ child = *(*task_ptr).children_data.add(i);
228
+ i += 1;
229
+ if !child.is_null() {
230
+ break;
231
+ }
232
+ }
233
+
234
+ while !child.is_null() {
235
+ // Dispatch via typed CTaskVtable β€” every task's vtable starts
236
+ // with the same 8-slot layout, so this cast is always valid.
237
+ let vt = &*((*child).vtable as *const CTaskVtable);
238
+ (vt.handle_message)(child, sender, msg_type, size, data);
239
+
240
+ // Find next non-null child β€” re-read watermark each time
241
+ // because the handler may have added or removed siblings
242
+ child = core::ptr::null_mut();
243
+ loop {
244
+ let watermark = (*task_ptr).children_watermark as usize;
245
+ if i >= watermark {
246
+ break;
247
+ }
248
+ child = *(*task_ptr).children_data.add(i);
249
+ i += 1;
250
+ if !child.is_null() {
251
+ break;
252
+ }
253
+ }
254
+ }
255
+ }
256
  }
257
 
258
  // Blanket impl for any CTask<V>
crates/openwa-core/src/task/mod.rs CHANGED
@@ -14,7 +14,7 @@ pub mod turn_game;
14
  pub mod worm;
15
 
16
  pub use arrow::{CTaskArrow, CTaskArrowVTable};
17
- pub use base::{CTask, CTaskBfsIter, SharedDataIter, SharedDataNode, SharedDataTable, Task, Vtable};
18
  pub use cloud::{CTaskCloud, CTaskCloudVTable};
19
  pub use filter::{CTaskFilter, CTaskFilterVTable};
20
  pub use fire::{CTaskFire, CTaskFireVTable};
 
14
  pub mod worm;
15
 
16
  pub use arrow::{CTaskArrow, CTaskArrowVTable};
17
+ pub use base::{CTask, CTaskBfsIter, CTaskVtable, SharedDataIter, SharedDataNode, SharedDataTable, Task, Vtable};
18
  pub use cloud::{CTaskCloud, CTaskCloudVTable};
19
  pub use filter::{CTaskFilter, CTaskFilterVTable};
20
  pub use fire::{CTaskFire, CTaskFireVTable};
crates/openwa-wormkit/src/replacements/task/cloud.rs CHANGED
@@ -96,7 +96,7 @@ unsafe extern "thiscall" fn cloud_handle_message(
96
  _ => {}
97
  }
98
 
99
- // Call base CTask::HandleMessage for all non-render messages
100
  let base_handler: unsafe extern "thiscall" fn(*mut CTask, *mut CTask, u32, u32, *const u8) =
101
  core::mem::transmute(openwa_core::rebase::rb(va::CTASK_VT2_HANDLE_MESSAGE) as usize);
102
  base_handler(cloud.as_task_ptr_mut(), sender, msg_type, size, data);
 
96
  _ => {}
97
  }
98
 
99
+ // Broadcast to children via original WA CTask::HandleMessage
100
  let base_handler: unsafe extern "thiscall" fn(*mut CTask, *mut CTask, u32, u32, *const u8) =
101
  core::mem::transmute(openwa_core::rebase::rb(va::CTASK_VT2_HANDLE_MESSAGE) as usize);
102
  base_handler(cloud.as_task_ptr_mut(), sender, msg_type, size, data);
crates/openwa-wormkit/src/replacements/task/filter.rs CHANGED
@@ -12,8 +12,7 @@ use openwa_core::task::{CTask, Task};
12
  ///
13
  /// Messages with `msg_type < 98` are only forwarded if
14
  /// `subscription_table[msg_type] != 0`. Messages >= 98 always pass through.
15
- /// Forwarding means tail-calling base CTask::HandleMessage, which propagates
16
- /// the message down to child tasks.
17
  unsafe extern "thiscall" fn filter_handle_message(
18
  this: *mut CTaskFilter,
19
  sender: *mut CTask,
@@ -30,7 +29,7 @@ unsafe extern "thiscall" fn filter_handle_message(
30
  return; // message not subscribed β€” drop silently
31
  }
32
 
33
- // Forward to base CTask::HandleMessage (propagates to children)
34
  let base_handler: unsafe extern "thiscall" fn(*mut CTask, *mut CTask, u32, u32, *const u8) =
35
  core::mem::transmute(openwa_core::rebase::rb(va::CTASK_VT2_HANDLE_MESSAGE) as usize);
36
  base_handler(filter.as_task_ptr() as *mut CTask, sender, msg_type, size, data);
 
12
  ///
13
  /// Messages with `msg_type < 98` are only forwarded if
14
  /// `subscription_table[msg_type] != 0`. Messages >= 98 always pass through.
15
+ /// Forwarding calls `broadcast_message` which propagates down to child tasks.
 
16
  unsafe extern "thiscall" fn filter_handle_message(
17
  this: *mut CTaskFilter,
18
  sender: *mut CTask,
 
29
  return; // message not subscribed β€” drop silently
30
  }
31
 
32
+ // Broadcast to children via original WA CTask::HandleMessage
33
  let base_handler: unsafe extern "thiscall" fn(*mut CTask, *mut CTask, u32, u32, *const u8) =
34
  core::mem::transmute(openwa_core::rebase::rb(va::CTASK_VT2_HANDLE_MESSAGE) as usize);
35
  base_handler(filter.as_task_ptr() as *mut CTask, sender, msg_type, size, data);