feat: add CTaskVtable, broadcast_message, and retype children_data
Browse filesAdd 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 |
-
|
|
|
|
| 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 |
-
//
|
| 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
|
| 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 |
-
//
|
| 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);
|