diff --git a/README.md b/README.md index 98c9e3c..adb85a1 100644 --- a/README.md +++ b/README.md @@ -15,3 +15,4 @@ A set of utilities for [Jecs](https://github.com/ukendio/jecs) - [replicator](/lib/replicator.luau) - Keep track of all entities with the passed components and calculate differences - [ref](/lib/ref.luau) - Reference entities by key - [command_buffer](/lib/command_buffer.luau) - Buffer commands to prevent iterator invalidation +- [spawner](/lib/spawner.luau) - Spawn entities with required components diff --git a/lib/init.luau b/lib/init.luau index 4031c64..0d2a98d 100644 --- a/lib/init.luau +++ b/lib/init.luau @@ -3,18 +3,26 @@ local jecs = require("@jecs") local WORLD = require("./world") + local collect = require("./collect") export type collect_signal_like = collect.signal_like export type collect_verbose_signal_like = collect.signal_like + local command_buffer = require("./command_buffer") export type command_buffer = command_buffer.command_buffer + local handle = require("./handle") export type handle = handle.handle + local ref = require("./ref") + local replicator = require("./replicator") export type replicator = replicator.replicator export type changes = replicator.changes +local spawner = require("./spawner") +export type spawner = spawner.spawner + --- Set the world for all utilities. --- Should be called once per context before any utility is used. --- @param world jecs.World @@ -30,4 +38,5 @@ return { replicator = replicator, ref = ref, command_buffer = command_buffer, + spawner = spawner, } diff --git a/lib/spawner.luau b/lib/spawner.luau new file mode 100644 index 0000000..ccad166 --- /dev/null +++ b/lib/spawner.luau @@ -0,0 +1,49 @@ +--!strict +local spawner_type = require("./spawner_type") +local WORLD = require("./world").get +local handle = require("./handle") + +export type spawner = spawner_type.spawner + +--- Creates an entity spawner. +--- ```luau +--- local spawner = jecs_utils.spawner(components.part, components.velocity, components.position) +--- for _ = 1, 1000 do +--- spawner.spawn(part_template:Clone(), Vector3.zero, Vector3.zero) +--- end +--- ``` +--- @param ... T... -- Components to use. +--- @return spawner +local function spawner(...) + local components = { ... } + local world = WORLD() + + local function spawn(...) + local passed = { ... } + local entity = world:entity() + + for idx, component in components do + world:set(entity, component, passed[idx]) + end + + return entity + end + + local function spawn_with_handle(...) + local passed = { ... } + local entity = handle(world:entity()) + + for idx, component in components do + entity:set(component, passed[idx]) + end + + return entity + end + + return { + spawn = spawn, + spawn_with_handle = spawn_with_handle, + } +end + +return (spawner :: any) :: spawner_type.create_spawner diff --git a/lib/spawner_type.luau b/lib/spawner_type.luau new file mode 100644 index 0000000..cad1c59 --- /dev/null +++ b/lib/spawner_type.luau @@ -0,0 +1,391 @@ +--!strict +local jecs = require("@jecs") +type entity = jecs.Entity +type id = jecs.Id + +local handle = require("./handle") + +export type spawner = { + --- Creates an entity with the given components. + --- @param ... T... + --- @return entity + spawn: (T...) -> entity, + --- Creates an entity with the given components and returns a handle to it. + --- @param ... T... + --- @return handle + spawn_with_handle: (T...) -> handle.handle, +} + +-- Very beautiful type incoming! +-- Sadly this has to be done, components are of different types than their values (`entity` vs `T`) +export type create_spawner = + ((id) -> spawner) + & ((id, id) -> spawner) + & ((id, id, id) -> spawner) + & ((id, id, id, id) -> spawner) + & ((id, id, id, id, id) -> spawner) + & ((id, id, id, id, id, id) -> spawner) + & ((id, id, id, id, id, id, id) -> spawner) + & (( + id, + id, + id, + id, + id, + id, + id, + id + ) -> spawner) + & (( + id, + id, + id, + id, + id, + id, + id, + id, + id + ) -> spawner) + & (( + id, + id, + id, + id, + id, + id, + id, + id, + id, + id + ) -> spawner) + & (( + id, + id, + id, + id, + id, + id, + id, + id, + id, + id, + id + ) -> spawner) + & (( + id, + id, + id, + id, + id, + id, + id, + id, + id, + id, + id, + id + ) -> spawner) + & (( + id, + id, + id, + id, + id, + id, + id, + id, + id, + id, + id, + id, + id + ) -> spawner) + & (( + id, + id, + id, + id, + id, + id, + id, + id, + id, + id, + id, + id, + id, + id + ) -> spawner) + & (( + id, + id, + id, + id, + id, + id, + id, + id, + id, + id, + id, + id, + id, + id, + id + ) -> spawner) + & (( + id, + id, + id, + id, + id, + id, + id, + id, + id, + id, + id, + id, + id, + id, + id, + id

, + id + ) -> spawner) + & (( + id, + id, + id, + id, + id, + id, + id, + id, + id, + id, + id, + id, + id, + id, + id, + id

, + id, + id + ) -> spawner) + & (( + id, + id, + id, + id, + id, + id, + id, + id, + id, + id, + id, + id, + id, + id, + id, + id

, + id, + id, + id + ) -> spawner) + & (( + id, + id, + id, + id, + id, + id, + id, + id, + id, + id, + id, + id, + id, + id, + id, + id

, + id, + id, + id, + id + ) -> spawner) + & (( + id, + id, + id, + id, + id, + id, + id, + id, + id, + id, + id, + id, + id, + id, + id, + id

, + id, + id, + id, + id, + id + ) -> spawner) + & (( + id, + id, + id, + id, + id, + id, + id, + id, + id, + id, + id, + id, + id, + id, + id, + id

, + id, + id, + id, + id, + id, + id + ) -> spawner) + & (( + id, + id, + id, + id, + id, + id, + id, + id, + id, + id, + id, + id, + id, + id, + id, + id

, + id, + id, + id, + id, + id, + id, + id + ) -> spawner) + & (( + id, + id, + id, + id, + id, + id, + id, + id, + id, + id, + id, + id, + id, + id, + id, + id

, + id, + id, + id, + id, + id, + id, + id, + id + ) -> spawner) + & (( + id, + id, + id, + id, + id, + id, + id, + id, + id, + id, + id, + id, + id, + id, + id, + id

, + id, + id, + id, + id, + id, + id, + id, + id, + id + ) -> spawner) + & (( + id, + id, + id, + id, + id, + id, + id, + id, + id, + id, + id, + id, + id, + id, + id, + id

, + id, + id, + id, + id, + id, + id, + id, + id, + id, + id + ) -> spawner) + +return {} diff --git a/test/tests.luau b/test/tests.luau index 8236117..a6b23cd 100644 --- a/test/tests.luau +++ b/test/tests.luau @@ -1,14 +1,17 @@ --!strict -- stylua: ignore start -local jecs = require("../jecs") +local jecs = require("@jecs") local jecs_utils = require("@jecs_utils") local testkit = require("@testkit") +type entity = jecs.Entity + local collect = jecs_utils.collect local handle = jecs_utils.handle local replicator = jecs_utils.replicator local ref = jecs_utils.ref local command_buffer = jecs_utils.command_buffer +local spawner = jecs_utils.spawner local signal = require("./signal") @@ -233,5 +236,51 @@ TEST("jecs_utils.command_buffer", function() end end) +TEST("jecs_utils.spawner()", function() + do CASE "spawn" + local world = jecs.World.new() + jecs_utils.initialize(world) + + local c1: entity = world:component() + local c2: entity = world:component() + local c3: entity<{}> = world:component() + + local t1 = world:entity() + + local entity_spawner = spawner(c1, c2, c3, t1) + + local tbl = {} + + local idx = entity_spawner.spawn(1234, "abcdef", tbl) + CHECK(world:contains(idx)) + CHECK(world:get(idx, c1) == 1234) + CHECK(world:get(idx, c2) == "abcdef") + CHECK(world:get(idx, c3) == tbl) + CHECK(world:has(idx, t1)) + end + + do CASE "spawn_with_handle" + local world = jecs.World.new() + jecs_utils.initialize(world) + + local c1: entity = world:component() + local c2: entity = world:component() + local c3: entity<{}> = world:component() + + local t1 = world:entity() + + local entity_spawner = spawner(c1, c2, c3, t1) + + local tbl = {} + + local ent = entity_spawner.spawn_with_handle(1234, "abcdef", tbl) + CHECK(world:contains(ent:id())) + CHECK(ent:get(c1) == 1234) + CHECK(ent:get(c2) == "abcdef") + CHECK(ent:get(c3) == tbl) + CHECK(ent:has(t1)) + end +end) + FINISH() -- stylua: ignore end diff --git a/wally.toml b/wally.toml index 4e97b0d..8be4314 100644 --- a/wally.toml +++ b/wally.toml @@ -1,6 +1,6 @@ [package] name = "mark-marks/jecs-utils" -version = "0.1.3" +version = "0.1.4" registry = "https://github.com/UpliftGames/wally-index" realm = "shared" license = "MIT"

+ ) -> spawner) + & (( + id, + id, + id, + id, + id, + id, + id, + id, + id, + id, + id, + id, + id, + id, + id, + id