Skip to documentation
SLOP

tiny.sdfii.internals.command.sink

Reference tiny.sdfii internals command sink

Defined in internals.command.

API (25)

Actions

Public operations.

Types and contracts

Public types and contracts.

Values and defaults

Public values and defaults.

No direct callersNo direct callsinternals.commandsink
Static calls · unresolved targets: unknown · external targets: unknown.

Source

Called byCallsNo direct callsprivate; no linkfun.sdfii.src.engine.abi.commands.directsdfii engine apply commandstest; no linkfun.sdfii.src.engine.command.sinktest: typed command list commits atom...test; no linkfun.sdfii.src.engine.command.sinktest: typed command list reports spaw...test; no linkfun.sdfii.src.engine.command.sinktest: typed command list rolls back e...private; no linkfun.sdfii.src.properties.commandsgeneratedCommandsPreserveWorldModelinternals.command.sinkapplyCommands
Static calls · unresolved targets: 0 · external targets: 10.
Called byCallsNo direct callsprivate; no linkfun.sdfii.src.engine.abi.commands.directsdfii engine attach physicsinternals.command.sinkattachPhysicsBlob
Static calls · unresolved targets: 0 · external targets: 4.
Called byCallsNo direct callsprivate; no linkfun.sdfii.src.engine.abi.commands.directsdfii engine despawninternals.command.sinkdespawn
Static calls · unresolved targets: 0 · external targets: 4.
Called byCallsNo direct callsprivate; no linkfun.sdfii.src.engine.abi.commands.directsdfii engine emitinternals.command.sinkemit
Static calls · unresolved targets: 0 · external targets: 6.
Called byCallsNo direct callsprivate; no linkfun.sdfii.src.engine.abi.commands.directsdfii engine set transformtest; no linkfun.sdfii.src.engine.command.sinktest: direct command sink spawns and ...internals.command.sinksetTransform
Static calls · unresolved targets: 0 · external targets: 4.
Called byCallsNo direct callsprivate; no linkfun.sdfii.src.engine.abi.commands.directsdfii engine spawntest; no linkfun.sdfii.src.engine.command.sinktest: direct command sink spawns and ...test; no linkfun.sdfii.src.engine.system.world.storetest: engine play mode routes command...internals.command.sinkspawn
Static calls · unresolved targets: 0 · external targets: 4.
Called byCallsNo direct callsprivate; no linkfun.sdfii.src.engine.abi.commands.directsdfii engine spawn manytest; no linkfun.sdfii.src.engine.command.sinktest: direct command sink preserves b...test; no linkfun.sdfii.src.engine.command.sinktest: spawn many rejects a late inval...internals.command.sinkspawnMany
Static calls · unresolved targets: 0 · external targets: 4.
Called byCallsNo direct callersprivate; no linkfun.sdfii.src.engine.command.sinkmapErrorinternals.command.sinksubmitBatch
Static calls · unresolved targets: 0 · external targets: 2.

Source: fun/sdfii/src/engine/command/root.zig:1

zig
pub const sink = @import("sink.zig");

Source: fun/sdfii/src/engine/command/sink.zig

zig
const std = @import("std");const ecs = @import("sdfii_ecs");const sdfii_world = @import("sdfii_world");const WorldStore = sdfii_world.store;const Commands = sdfii_world.commands;const Schema = sdfii_world.schema;const Self = @This();pub const EntityId = ecs.EntityId;pub const TransformDesc = ecs.SdfiiTransformDesc;pub const physics_component_schema_id: u64 = @backingInt(Schema.ComponentSchema.physics);pub const SpawnSpec = struct {    name: ?[]const u8 = null,    key: ?[]const u8 = null,    transform: ?TransformDesc = null,};pub const PhysicsBlobSpec = struct {    entity: EntityId,    payload: []const u8,};pub const TransformSpec = struct {    entity: EntityId,    transform: TransformDesc,};pub const EventSpec = struct {    schema_id: u64,    payload: []const u8 = &.{},};pub const Command = union(enum) {    spawn: SpawnSpec,    despawn: EntityId,    attach_physics_blob: PhysicsBlobSpec,    set_transform: TransformSpec,    emit: EventSpec,};pub const CommandKind = enum(u8) {    spawn = 1,    despawn = 2,    attach_physics = 3,    set_transform = 4,    emit = 5,};pub const Error = error{    BatchTooLarge,    InvalidUtf8,    SpawnBufferTooSmall,    SubmitFailed,    OutOfMemory,};pub const BatchResult = struct {    command_count: u32,    spawned_count: u32,};pub const SpawnResult = struct {    entity: EntityId,    command_count: u32,};pub const SpawnManyResult = struct {    count: usize,    command_count: u32,};pub const MutationResult = struct {    command_count: u32,};pub const EventResult = struct {    command_count: u32,    visible_event_count: usize,};pub const ApplyResult = struct {    command_count: usize,    spawned_count: usize,    event_count: usize,    visible_event_count: usize,};allocator: std.mem.Allocator,world: *WorldStore,pub fn bind(allocator: std.mem.Allocator, world: *WorldStore) Self {    return .{        .allocator = allocator,        .world = world,    };}pub fn submitBatch(self: *const Self, batch: *Commands.Batch, spawned_ids: ?[]EntityId) Error!BatchResult {    const command_count = batch.count();    const spawned_count = self.world.submit(batch, spawned_ids) catch |err| return mapError(err);    return .{        .command_count = command_count,        .spawned_count = spawned_count,    };}pub fn spawn(self: *const Self, spec: SpawnSpec) Error!SpawnResult {    var batch = try Commands.Batch.init(self.allocator, 4);    defer batch.deinit();    try batch.pushSpawn(spec.name, spec.key, spec.transform);    var spawned: [1]EntityId = .{.{ .index = std.math.maxInt(u32), .generation = 0 }};    const result = try self.submitBatch(&batch, spawned[0..]);    std.debug.assert(result.spawned_count == 1);    return .{        .entity = spawned[0],        .command_count = result.command_count,    };}pub fn spawnMany(self: *const Self, specs: []const SpawnSpec, out: []EntityId) Error!SpawnManyResult {    if (out.len < specs.len) return error.SpawnBufferTooSmall;    if (specs.len == 0) return .{ .count = 0, .command_count = 0 };    const capacity_hint: u32 = std.math.cast(u32, specs.len) orelse return error.BatchTooLarge;    var batch = try Commands.Batch.init(self.allocator, @max(capacity_hint, @as(u32, 4)));    defer batch.deinit();    for (specs) |spec| {        try batch.pushSpawn(spec.name, spec.key, spec.transform);    }    const result = try self.submitBatch(&batch, out[0..specs.len]);    const spawned_count: usize = @intCast(result.spawned_count);    std.debug.assert(spawned_count == specs.len);    return .{        .count = spawned_count,        .command_count = result.command_count,    };}pub fn despawn(self: *const Self, entity: EntityId) Error!MutationResult {    var batch = try Commands.Batch.init(self.allocator, 4);    defer batch.deinit();    try batch.pushDespawn(entity);    const result = try self.submitBatch(&batch, null);    return .{ .command_count = result.command_count };}pub fn attachPhysicsBlob(self: *const Self, entity: EntityId, payload: []const u8) Error!MutationResult {    var batch = try Commands.Batch.init(self.allocator, 4);    defer batch.deinit();    try batch.pushUpsertComponent(entity, physics_component_schema_id, payload);    const result = try self.submitBatch(&batch, null);    return .{ .command_count = result.command_count };}pub fn setTransform(self: *const Self, entity: EntityId, transform: TransformDesc) Error!MutationResult {    var batch = try Commands.Batch.init(self.allocator, 4);    defer batch.deinit();    try batch.pushSetTransform(entity, transform);    const result = try self.submitBatch(&batch, null);    return .{ .command_count = result.command_count };}pub fn emit(self: *const Self, schema_id: u64, payload: []const u8) Error!EventResult {    var batch = try Commands.Batch.init(self.allocator, 4);    defer batch.deinit();    try batch.pushEnqueueEvent(schema_id, payload);    try batch.pushPublishEvents();    const result = try self.submitBatch(&batch, null);    return .{        .command_count = result.command_count,        .visible_event_count = self.world.visibleEventCount(),    };}pub fn applyCommands(    self: *const Self,    commands: []const Command,    spawned_out: []EntityId,) Error!ApplyResult {    var expected_spawn_count: usize = 0;    var event_count: usize = 0;    for (commands) |command| {        switch (command) {            .spawn => expected_spawn_count += 1,            .emit => event_count += 1,            .despawn, .attach_physics_blob, .set_transform => {},        }    }    if (spawned_out.len < expected_spawn_count) return error.SpawnBufferTooSmall;    if (commands.len == 0) return .{        .command_count = 0,        .spawned_count = 0,        .event_count = 0,        .visible_event_count = self.world.visibleEventCount(),    };    const internal_count = std.math.add(usize, commands.len, @intFromBool(event_count != 0)) catch        return error.BatchTooLarge;    const capacity_hint = std.math.cast(u32, internal_count) orelse return error.BatchTooLarge;    var batch = try Commands.Batch.init(self.allocator, @max(capacity_hint, @as(u32, 4)));    defer batch.deinit();    for (commands) |command| {        switch (command) {            .spawn => |spec| try batch.pushSpawn(spec.name, spec.key, spec.transform),            .despawn => |entity| try batch.pushDespawn(entity),            .attach_physics_blob => |spec| try batch.pushUpsertComponent(                spec.entity,                physics_component_schema_id,                spec.payload,            ),            .set_transform => |spec| try batch.pushSetTransform(spec.entity, spec.transform),            .emit => |spec| try batch.pushEnqueueEvent(spec.schema_id, spec.payload),        }    }    if (event_count != 0) try batch.pushPublishEvents();    const result = try self.submitBatch(&batch, spawned_out[0..expected_spawn_count]);    const spawned_count: usize = @intCast(result.spawned_count);    std.debug.assert(spawned_count == expected_spawn_count);    return .{        .command_count = commands.len,        .spawned_count = spawned_count,        .event_count = event_count,        .visible_event_count = self.world.visibleEventCount(),    };}fn mapError(err: anyerror) Error {    return switch (err) {        error.OutOfMemory => error.OutOfMemory,        error.InvalidUtf8 => error.InvalidUtf8,        error.SpawnBufferTooSmall => error.SpawnBufferTooSmall,        error.BatchTooLarge => error.BatchTooLarge,        else => error.SubmitFailed,    };}test "direct command sink spawns and updates engine-owned storage" {    const allocator = std.testing.allocator;    var world = try WorldStore.init(allocator, 8);    defer world.deinit();    world.markEngineOwned();    const sink = Self.bind(allocator, &world);    const spawned = try sink.spawn(.{        .name = "player",        .key = "hero",        .transform = .{ .pos_x = 1, .pos_y = 2, .pos_z = 3 },    });    try std.testing.expectEqual(@as(u32, 1), spawned.command_count);    try std.testing.expect(world.isAlive(spawned.entity));    const updated = try sink.setTransform(spawned.entity, .{ .pos_x = 4, .pos_y = 5, .pos_z = 6 });    try std.testing.expectEqual(@as(u32, 1), updated.command_count);    const stored = world.transformDesc(spawned.entity).?;    try std.testing.expectEqual(@as(f32, 4), stored.pos_x);    try std.testing.expectEqual(@as(f32, 5), stored.pos_y);    try std.testing.expectEqual(@as(f32, 6), stored.pos_z);}test "direct command sink preserves batch ordering" {    const allocator = std.testing.allocator;    var world = try WorldStore.init(allocator, 8);    defer world.deinit();    world.markEngineOwned();    const sink = Self.bind(allocator, &world);    const specs = [_]SpawnSpec{        .{ .name = "a", .transform = .{ .pos_x = 1 } },        .{ .key = "b" },        .{ .name = "c", .transform = .{ .pos_z = 3 } },    };    var out: [3]EntityId = undefined;    const result = try sink.spawnMany(&specs, &out);    try std.testing.expectEqual(@as(usize, 3), result.count);    try std.testing.expectEqual(@as(u32, 3), result.command_count);    for (out) |entity| {        try std.testing.expect(world.isAlive(entity));    }    try std.testing.expectEqual(@as(f32, 1), world.transformDesc(out[0]).?.pos_x);    try std.testing.expect(world.transformDesc(out[1]) == null);    try std.testing.expectEqual(@as(f32, 3), world.transformDesc(out[2]).?.pos_z);}test "spawn many rejects a late invalid spec without touching world or output" {    const allocator = std.testing.allocator;    var world = try WorldStore.init(allocator, 8);    defer world.deinit();    world.markEngineOwned();    const sink = Self.bind(allocator, &world);    const invalid_utf8 = [_]u8{0xff};    const specs = [_]SpawnSpec{        .{ .name = "valid" },        .{ .name = &invalid_utf8 },    };    const sentinel = EntityId{ .index = std.math.maxInt(u32), .generation = std.math.maxInt(u32) };    var out: [2]EntityId = .{ sentinel, sentinel };    try std.testing.expectError(error.InvalidUtf8, sink.spawnMany(&specs, &out));    try std.testing.expectEqual(@as(u32, 0), world.aliveCount());    try std.testing.expectEqual(sentinel, out[0]);    try std.testing.expectEqual(sentinel, out[1]);}test "typed command list reports spawned and emitted commands" {    const allocator = std.testing.allocator;    var world = try WorldStore.init(allocator, 8);    defer world.deinit();    world.markEngineOwned();    const sink = Self.bind(allocator, &world);    const commands = [_]Command{        .{ .spawn = .{ .name = "a" } },        .{ .emit = .{ .schema_id = 9, .payload = "evt" } },    };    var spawned: [1]EntityId = undefined;    const result = try sink.applyCommands(&commands, &spawned);    try std.testing.expectEqual(@as(usize, 2), result.command_count);    try std.testing.expectEqual(@as(usize, 1), result.spawned_count);    try std.testing.expectEqual(@as(usize, 1), result.event_count);    try std.testing.expect(world.isAlive(spawned[0]));    try std.testing.expectEqual(@as(usize, 1), world.visibleEventCount());}test "typed command list commits atomically and publishes all emitted events" {    const allocator = std.testing.allocator;    var world = try WorldStore.init(allocator, 8);    defer world.deinit();    world.markEngineOwned();    const sink = Self.bind(allocator, &world);    const invalid_utf8 = [_]u8{0xff};    const rejected = [_]Command{        .{ .spawn = .{ .name = "would-commit-first" } },        .{ .spawn = .{ .name = &invalid_utf8 } },    };    const rejected_sentinel = EntityId{        .index = std.math.maxInt(u32),        .generation = std.math.maxInt(u32),    };    var rejected_spawns: [2]EntityId = .{ rejected_sentinel, rejected_sentinel };    try std.testing.expectError(error.InvalidUtf8, sink.applyCommands(&rejected, &rejected_spawns));    try std.testing.expectEqual(@as(u32, 0), world.aliveCount());    try std.testing.expectEqual(rejected_sentinel, rejected_spawns[0]);    try std.testing.expectEqual(rejected_sentinel, rejected_spawns[1]);    const accepted = [_]Command{        .{ .emit = .{ .schema_id = 7, .payload = "first" } },        .{ .emit = .{ .schema_id = 8, .payload = "second" } },    };    var no_spawns: [0]EntityId = .{};    const result = try sink.applyCommands(&accepted, &no_spawns);    try std.testing.expectEqual(@as(usize, 2), result.event_count);    try std.testing.expectEqual(@as(usize, 2), result.visible_event_count);    try std.testing.expectEqual(@as(u64, 7), world.visibleEvent(0).?.schema_id);    try std.testing.expectEqual(@as(u64, 8), world.visibleEvent(1).?.schema_id);}test "typed command list rolls back every allocation failure and retries" {    var saw_operation_failure = false;    var completed_sweep = false;    for (0..512) |fail_index| {        var failing = std.testing.FailingAllocator.init(std.testing.allocator, .{ .fail_index = fail_index });        var world = WorldStore.init(failing.allocator(), 2) catch |err| {            try std.testing.expectEqual(error.OutOfMemory, err);            try std.testing.expectEqual(failing.allocated_bytes, failing.freed_bytes);            continue;        };        const existing = world.entities.spawn() catch |err| {            try std.testing.expectEqual(error.OutOfMemory, err);            world.deinit();            try std.testing.expectEqual(failing.allocated_bytes, failing.freed_bytes);            continue;        };        world.transforms.insert(existing.index, .{ .pos_x = 1 }) catch |err| {            try std.testing.expectEqual(error.OutOfMemory, err);            world.deinit();            try std.testing.expectEqual(failing.allocated_bytes, failing.freed_bytes);            continue;        };        world.markEngineOwned();        const sink = Self.bind(failing.allocator(), &world);        const commands = [_]Command{            .{ .set_transform = .{ .entity = existing, .transform = .{ .pos_x = 9 } } },            .{ .emit = .{ .schema_id = 42, .payload = "event" } },            .{ .spawn = .{ .name = "spawned", .transform = .{ .pos_z = 3 } } },        };        const sentinel = EntityId{ .index = std.math.maxInt(u32), .generation = std.math.maxInt(u32) };        var spawned: [1]EntityId = .{sentinel};        const attempt = sink.applyCommands(&commands, &spawned);        if (attempt) |result| {            try std.testing.expect(!failing.has_induced_failure);            try expectMixedBatchResult(&world, existing, spawned[0], result);            completed_sweep = true;        } else |err| {            try std.testing.expectEqual(error.OutOfMemory, err);            try std.testing.expect(failing.has_induced_failure);            saw_operation_failure = true;            try std.testing.expectEqual(@as(u32, 1), world.aliveCount());            try std.testing.expectEqual(@as(f32, 1), world.transformDesc(existing).?.pos_x);            try std.testing.expectEqual(@as(usize, 0), world.visibleEventCount());            try std.testing.expectEqual(@as(usize, 0), world.pendingEventCount());            try std.testing.expectEqual(sentinel, spawned[0]);            failing.fail_index = std.math.maxInt(usize);            failing.resize_fail_index = std.math.maxInt(usize);            const result = try sink.applyCommands(&commands, &spawned);            try expectMixedBatchResult(&world, existing, spawned[0], result);        }        world.deinit();        try std.testing.expectEqual(failing.allocated_bytes, failing.freed_bytes);        if (completed_sweep) break;    }    try std.testing.expect(saw_operation_failure);    try std.testing.expect(completed_sweep);}fn expectMixedBatchResult(    world: *const WorldStore,    existing: EntityId,    spawned: EntityId,    result: ApplyResult,) !void {    try std.testing.expectEqual(@as(usize, 3), result.command_count);    try std.testing.expectEqual(@as(usize, 1), result.spawned_count);    try std.testing.expectEqual(@as(usize, 1), result.event_count);    try std.testing.expectEqual(@as(usize, 1), result.visible_event_count);    try std.testing.expectEqual(@as(u32, 2), world.aliveCount());    try std.testing.expectEqual(@as(f32, 9), world.transformDesc(existing).?.pos_x);    try std.testing.expect(world.isAlive(spawned));    try std.testing.expectEqual(@as(f32, 3), world.transformDesc(spawned).?.pos_z);    try std.testing.expectEqual(@as(u64, 42), world.visibleEvent(0).?.schema_id);}

Audit

Definitions26
Public names26
Members37
Version26.7.0
Revisiondaab053ee433