Currently, bakery-game has component files that just wrap task engine calls:
// bakery-game/components/storage.zig
pub const Storage = struct {
storage_type: StorageType,
initial_item: ?Item,
accepts: ?Item,
pub fn onAdd(payload: engine.ComponentPayload) void {
// ... lots of code to register with task engine
task_state.addStorage(entity_id, config);
}
};Every component needs an onAdd hook that:
- Gets the game/registry
- Extracts component data
- Calls task engine registration
- Logs debug info
- Game → Tasks: Component callbacks call
task_state.addStorage(), etc. - Tasks → Game: Hooks like
item_deliveredrequire game to storegame_ptr
// bakery-game/components/task_state.zig
var game_ptr: ?*engine.Game = null;
pub fn setGame(game: *engine.Game) void {
game_ptr = game;
}
pub fn item_delivered(payload: anytype) void {
const game = game_ptr orelse return;
game.setPositionXY(item_entity, storage_pos.x, storage_pos.y);
}The same conceptual data exists in multiple places:
- Storage component on entity
- Storage state in task engine
- Must be kept in sync manually
// labelle-tasks would export components directly
const tasks = @import("labelle-tasks");
// Game uses task components in prefabs
pub const Components = engine.ComponentRegistry(struct {
pub const Storage = tasks.Storage; // From tasks!
pub const Worker = tasks.Worker; // From tasks!
pub const Workstation = tasks.Workstation;
});// During game init
var task_engine = tasks.Engine.init(allocator, .{
.registry = game.getRegistry(), // Direct ECS access
.distance_fn = myDistanceFn, // Already exists - calculates distance between entities
});
// Tasks registers its own ECS observers
task_engine.attachToECS(); // Sets up component callbacks internallyNote: Tasks already uses a distance callback for pathfinding decisions. It doesn't need direct position access - the game provides the distance function during initialization.
// Inside labelle-tasks
pub const Storage = struct {
storage_type: StorageType,
initial_item: ?Item,
accepts: ?Item,
// Tasks handles its own registration
// No game-side onAdd needed
};
// Engine auto-registers when component is added to entity
fn onStorageAdded(entity: Entity, storage: *Storage) void {
self.registerStorage(entity, storage);
}Tasks would need to work with engine's ECS abstraction layer, not a specific backend.
// Tasks needs to accept the engine's Registry type
pub fn Engine(comptime Registry: type) type {
return struct {
registry: *Registry,
// ...
};
}If tasks imports engine for ECS types, and engine imports tasks for components...
Solution: Tasks defines component data only, engine provides the observer/callback mechanism:
// tasks/components.zig - pure data, no engine dependency
pub const StorageData = struct {
storage_type: StorageType,
initial_item: ?Item,
};
// engine integrates via its component system
const Storage = engine.ComponentWith(tasks.StorageData, .{
.onAdd = tasks.onStorageAdded,
});Tasks still needs to notify the game of events (item_delivered, worker_assigned, etc.). These hooks remain necessary for game-side reactions like:
- Moving item visuals to storage position
- Playing animations
- Spawning prefabs
- Zero boilerplate components - Use tasks components directly
- Single source of truth - Component IS the task state
- Automatic sync - ECS observers handle registration
- Cleaner game code - No wrapper components needed
// Before: bakery-game needs 6 component files
components/
storage.zig // Wrapper + onAdd
worker.zig // Wrapper + onAdd
workstation.zig // Wrapper + onAdd
dangling_item.zig
task_state.zig // Global state + hooks
items.zig
// After: Just use tasks components
pub const Components = engine.ComponentRegistry(struct {
pub const Storage = tasks.Storage;
pub const Worker = tasks.Worker;
pub const Workstation = tasks.Workstation;
pub const DanglingItem = tasks.DanglingItem;
pub const Items = items.Items; // Game-specific only
});- Should tasks be tightly coupled to engine's ECS, or stay abstract?
- How to handle game-specific extensions to task components?
- Should tasks own entity creation (spawn prefabs) or just state?
- How to handle tasks needing game-specific types (Item enum)?
- Prototype tasks exporting a component with ECS observer
- Test if engine can import and use task components
- Measure boilerplate reduction
- Document the integration pattern