From 150afd784a235c2370a8950b899008c0075f723b Mon Sep 17 00:00:00 2001 From: Ukendio Date: Wed, 7 May 2025 19:21:12 +0200 Subject: [PATCH 1/8] Allow pre existing hooks for observer --- addons/observers.luau | 135 ++++++++---------- .../systems/players_added.luau | 10 +- test/addons/observers.luau | 27 +++- test/tests.luau | 24 ++++ wally.toml | 2 +- 5 files changed, 108 insertions(+), 90 deletions(-) diff --git a/addons/observers.luau b/addons/observers.luau index 5f3f1f5..ef85d85 100644 --- a/addons/observers.luau +++ b/addons/observers.luau @@ -6,9 +6,9 @@ type Observer = { } export type PatchedWorld = jecs.World & { - added: (PatchedWorld, jecs.Id, (e: jecs.Entity, id: jecs.Id, value: any) -> ()) -> (), - removed: (PatchedWorld, jecs.Id, (e: jecs.Entity, id: jecs.Id) -> ()) -> (), - changed: (PatchedWorld, jecs.Id, (e: jecs.Entity, id: jecs.Id) -> ()) -> (), + added: (PatchedWorld, jecs.Id, (e: jecs.Entity, id: jecs.Id, value: T) -> ()) -> () -> (), + removed: (PatchedWorld, jecs.Id, (e: jecs.Entity, id: jecs.Id) -> ()) -> () -> (), + changed: (PatchedWorld, jecs.Id, (e: jecs.Entity, id: jecs.Id, value: T) -> ()) -> () -> (), observer: (PatchedWorld, Observer) -> (), monitor: (PatchedWorld, Observer) -> (), } @@ -70,6 +70,7 @@ local function join(world, component) sparse_array[entity] = nil dense_array[max_id] = nil values[max_id] = nil + max_id -= 1 end) world:changed(component, function(entity, id, value) @@ -89,62 +90,6 @@ local function join(world, component) end end -local function query_changed(world, component) - assert(jecs.IS_PAIR(component) == false) - local callerid = debug.info(2, "sl") - - local tracker = world.trackers[callerid] - if not tracker then - local records = {} - local connections = {} - tracker = { - records = records, - connections = connections - } - world.trackers[callerid] = tracker - - table.insert(connections, world:added(component, function(entity, id, v) - tracker[entity] = { - new = v - } - end)) - table.insert(connections, world:changed(component, function(entity, id, v) - local record = tracker[entity] - record.old = record.new - record.new = v - end)) - - table.insert(connections, world:removed(component, function(entity, id) - local record = tracker[entity] - record.old = record.new - record.new = nil - end)) - end - - local entity = nil - local record = nil - return function() - entity, record = next(tracker, entity) - if entity == nil then - return - end - return entity, record - end -end - -local function spy_on_world_delete(world) - local world_delete = world.delete - world.delete = function(world, entity) - world_delete(world, entity) - for _, tracker in world.trackers do - tracker.records[entity] = nil - for _, connection in tracker.connections do - connection() - end - end - end -end - local function monitors_new(world, description) local query = description.query local callback = description.callback @@ -192,18 +137,23 @@ local function monitors_new(world, description) end end -local function observers_add(world: jecs.World & { [string]: any }): PatchedWorld +local function observers_add(world: jecs.World): PatchedWorld type Signal = { [jecs.Entity]: { (...any) -> () } } + + local world_mut = world :: jecs.World & {[string]: any} + local signals = { added = {} :: Signal, emplaced = {} :: Signal, removed = {} :: Signal } - world.added = function(_, component, fn) + world_mut.added = function( + _: jecs.World, + component: jecs.Id, + fn: (e: jecs.Entity, id: jecs.Id, value: T) -> () + ) local listeners = signals.added[component] - local component_index = world.component_index :: jecs.ComponentIndex - assert(component_index[component] == nil, "You cannot use hooks on components you intend to use this signal with") if not listeners then listeners = {} signals.added[component] = listeners @@ -213,7 +163,16 @@ local function observers_add(world: jecs.World & { [string]: any }): PatchedWorl listener(entity, id, value) end end - world:set(component, jecs.OnAdd, on_add) + local idr = world.component_index[component] + if idr then + local idr_hook_existing = idr.hooks.on_add + if idr_hook_existing then + table.insert(listeners, idr_hook_existing) + end + idr.hooks.on_add = on_add :: any + else + world:set(component, jecs.OnAdd, on_add) + end end table.insert(listeners, fn) return function() @@ -224,10 +183,12 @@ local function observers_add(world: jecs.World & { [string]: any }): PatchedWorl end end - world.changed = function(_, component, fn) + world_mut.changed = function( + _: jecs.World, + component: jecs.Id, + fn: (e: jecs.Entity, id: jecs.Id, value: T) -> () + ) local listeners = signals.emplaced[component] - local component_index = world.component_index :: jecs.ComponentIndex - assert(component_index[component] == nil, "You cannot use hooks on components you intend to use this signal with") if not listeners then listeners = {} signals.emplaced[component] = listeners @@ -236,7 +197,16 @@ local function observers_add(world: jecs.World & { [string]: any }): PatchedWorl listener(entity, id, value) end end - world:set(component, jecs.OnChange, on_change) + local idr = world.component_index[component] + if idr then + local idr_hook_existing = idr.hooks.on_change + if idr_hook_existing then + table.insert(listeners, idr_hook_existing) + end + idr.hooks.on_change = on_change :: any + else + world:set(component, jecs.OnChange, on_change) + end end table.insert(listeners, fn) return function() @@ -247,10 +217,12 @@ local function observers_add(world: jecs.World & { [string]: any }): PatchedWorl end end - world.removed = function(_, component, fn) + world_mut.removed = function( + _: jecs.World, + component: jecs.Id, + fn: (e: jecs.Entity, id: jecs.Id) -> () + ) local listeners = signals.removed[component] - local component_index = world.component_index :: jecs.ComponentIndex - assert(component_index[component] == nil, "You cannot use hooks on components you intend to use this signal with") if not listeners then listeners = {} signals.removed[component] = listeners @@ -259,7 +231,16 @@ local function observers_add(world: jecs.World & { [string]: any }): PatchedWorl listener(entity, id, value) end end - world:set(component, jecs.OnRemove, on_remove) + local idr = world.component_index[component] + if idr then + local idr_hook_existing = idr.hooks.on_remove + if idr_hook_existing then + table.insert(listeners, idr_hook_existing) + end + idr.hooks.on_remove = on_remove :: any + else + world:set(component, jecs.OnRemove, on_remove) + end end table.insert(listeners, fn) return function() @@ -270,15 +251,15 @@ local function observers_add(world: jecs.World & { [string]: any }): PatchedWorl end end - world.signals = signals + world_mut.signals = signals - world.observer = observers_new + world_mut.observer = observers_new - world.monitor = monitors_new + world_mut.monitor = monitors_new - world.trackers = {} + world_mut.trackers = {} - return world :: PatchedWorld + return world_mut :: PatchedWorld end return observers_add diff --git a/demo/src/ServerScriptService/systems/players_added.luau b/demo/src/ServerScriptService/systems/players_added.luau index 703c98d..46b1ab1 100644 --- a/demo/src/ServerScriptService/systems/players_added.luau +++ b/demo/src/ServerScriptService/systems/players_added.luau @@ -1,7 +1,6 @@ -local ReplicatedStorage = game:GetService("ReplicatedStorage") -local collect = require(ReplicatedStorage.collect) -local types = require(ReplicatedStorage.types) -local ct = require(ReplicatedStorage.components) +local collect = require("../../ReplicatedStorage/collect") +local types = require("../../ReplicatedStorage/types") +local ct = require("../../ReplicatedStorage/components") local Players = game:GetService("Players") local player_added = collect(Players.PlayerAdded) @@ -13,7 +12,8 @@ return function(world: types.World, dt: number) for entity, player in world:query(ct.Player):without(ct.Renderable) do local character = player.Character - if character and character.Parent ~= nil then + if character then + if not character.Parent then world:set(entity, ct.Renderable, character) end end diff --git a/test/addons/observers.luau b/test/addons/observers.luau index 26461c7..1d5a99e 100644 --- a/test/addons/observers.luau +++ b/test/addons/observers.luau @@ -8,6 +8,24 @@ local observers_add = require("@addons/observers") TEST("addons/observers", function() local world = observers_add(jecs.world()) + do CASE "Should not override hook" + local A = world:component() + + local count = 0 + local function counter() + count += 1 + end + + world:set(A, jecs.OnAdd, counter) + world:set(world:entity(), A, true) + CHECK(count == 1) + world:added(A, counter) + world:set(world:entity(), A, true) + + CHECK(count == 3) + print(count) + end + do CASE "Ensure ordering between signals and observers" local A = world:component() local B = world:component() @@ -24,17 +42,12 @@ TEST("addons/observers", function() world:added(A, counter) world:added(A, counter) - world:removed(A, counter) - local e = world:entity() world:add(e, A) CHECK(count == 2) world:add(e, B) CHECK(count == 3) - - world:remove(e, A) - CHECK(count == 4) end do CASE "Rematch entities in observers" @@ -87,10 +100,10 @@ TEST("addons/observers", function() local A = world:component() local callcount = 0 - world:added(A, function(entity) + world:added(A, function(entity) callcount += 1 end) - world:added(A, function(entity) + world:added(A, function(entity) callcount += 1 end) diff --git a/test/tests.luau b/test/tests.luau index c87c7b6..61c089d 100644 --- a/test/tests.luau +++ b/test/tests.luau @@ -25,6 +25,30 @@ local entity_visualiser = require("@tools/entity_visualiser") local lifetime_tracker_add = require("@tools/lifetime_tracker") local dwi = entity_visualiser.stringify +TEST("repro", function() + local Model = jecs.component() + local Relation = jecs.component() + local Relation2 = jecs.component() + + local world = jecs.world() + + local e1 = world:entity() + world:set(e1, Model, 2) + + local e2 = world:entity() + world:set(e2, Model, 2) + world:set(e2, jecs.pair(Relation, e1), 5) + + local e3 = world:entity() + world:set(e3, Model, 2) + world:set(e3, jecs.pair(Relation, e1), 5) + + world:delete(e1) + + for _ in world:query(Model) do end + jecs.ECS_META_RESET() +end) + TEST("world:add()", function() do CASE "idempotent" local world = jecs.world() diff --git a/wally.toml b/wally.toml index 125c85b..af3bb5d 100644 --- a/wally.toml +++ b/wally.toml @@ -1,6 +1,6 @@ [package] name = "ukendio/jecs" -version = "0.5.5" +version = "0.6.0-rc.1" registry = "https://github.com/UpliftGames/wally-index" realm = "shared" license = "MIT" From ac4d9ade17bd3833560e3601e77fc3060366e9c9 Mon Sep 17 00:00:00 2001 From: EncodedVenom Date: Sat, 17 May 2025 13:59:19 -0400 Subject: [PATCH 2/8] Cleanup some todo stubs --- docs/contributing/guidelines.md | 22 ++++++++- docs/contributing/issues.md | 26 +++++++++- docs/contributing/pull-requests.md | 78 +++++++++++++++++++++++++++++- 3 files changed, 120 insertions(+), 6 deletions(-) diff --git a/docs/contributing/guidelines.md b/docs/contributing/guidelines.md index 2e03428..d0e3fcb 100644 --- a/docs/contributing/guidelines.md +++ b/docs/contributing/guidelines.md @@ -1,3 +1,21 @@ -## TODO +# Contribution Guidelines -This is a TODO stub. \ No newline at end of file +Whether you found an issue, or want to make a change to jecs, we'd love to hear back from the community on what features you want or bugs you've run into. + +There's a few different ways you can go about this. + +## Creating an Issue + +This is what you should be filing if you have a bug you want to report. + +[Click here](https://github.com/Ukendio/jecs/issues/new/choose) to file a bug report. We have a few templates ready for the most common issue types. + +Additionally, see the [Submitting Issues](/contributing/issues) page for more information. + +## Creating a Pull Request + +This is what you should be filing if you have a change you want to merge into the main project. + +[Click here](https://github.com/Ukendio/jecs/compare) to select the branch you want to merge from. + +Additionally, see the [Submitting Pull Requests](/contributing/pull-requests) page for more information. \ No newline at end of file diff --git a/docs/contributing/issues.md b/docs/contributing/issues.md index 2e03428..4db8d91 100644 --- a/docs/contributing/issues.md +++ b/docs/contributing/issues.md @@ -1,3 +1,25 @@ -## TODO +# Submitting Issues + +When you're submitting an issue, generally they fall into a few categories: + +## Bug + +We need some information to figure out what's going wrong. At a minimum, you need to tell us: + + (1) What's supposed to happen + + (2) What actually happened + + (3) Steps to reproduce + + +Stack traces and other useful information that you find make a bug report more likely to be fixed. + +Consult the template for a bug report if you don't know or have questions about how to format this. + +## Documentation + +Depending on how you go about it, this can be done as a [Pull Request](/contributing/pull-requests) instead of an issue. Generally, we need to know what was wrong, what you changed, and how it improved the documentation if it isn't obvious. + +We just need to know what's wrong. You should fill out a [PR](/contributing/pull-requests) if you know what should be there instead. -This is a TODO stub. \ No newline at end of file diff --git a/docs/contributing/pull-requests.md b/docs/contributing/pull-requests.md index 2e03428..674e2b0 100644 --- a/docs/contributing/pull-requests.md +++ b/docs/contributing/pull-requests.md @@ -1,3 +1,77 @@ -## TODO +# Submitting Pull Requests -This is a TODO stub. \ No newline at end of file +When submitting a Pull Request, there's a few reasons to do so: + + +## Documentation + +If there's something to change with the documentation, you should follow a similar format to this example: + +An example of an appropriate typo-fixing PR would be: + +>**Brief Description of your Changes** +> +>I fixed a couple of typos found in the /contributing/issues.md file. +> +>**Impact of your Changes** +> +>- Documentation is more clear and readable for the users. +> +>**Tests Performed** +> +>Ran `vitepress dev docs` and verified it was built successfully. +> +>**Additional Comments** +> +>[At Discretion] + +## Change in Behavior + +An example of an appropriate PR that adds a new feature would be: + +> +>**Brief Description of your Changes** +> +>I added `jecs.best_function`, which gives everyone who uses the module an immediate boost in concurrent player counts. (this is a joke) +> +>**Impact of your Changes** +> +>- jecs functionality is extended to better fit the needs of the community [explain why]. +> +>**Tests Performed** +> +>Added a few test cases to ensure the function runs as expected [link to changes]. +> +>**Additional Comments** +> +>[At Discretion] + +## Addons + +If you made something you think should be included into the [addons page](/learn/concepts/addons), let us know! + +We have tons of examples of libraries and other tools which can be used in conjunction with jecs on this page. + +One example of a PR that would be accepted is: + +>**Brief Description of your Changes** +> +>I added [jecs observers](/learn/concepts/addons#jecs_observers) to the addons page. +> +>**Impact of your Changes** +> +>- jecs observers are a different and important way of handling queries which benefit the users of jecs by [explain why your tool benefits users here] +> +>- [talk about why you went with this design instead of maybe an alternative] +> +>**Tests Performed** +> +> I used this tool in conjunction with jecs and ensured it works as expected. +> +> [If you wrote unit tests for your tool, mention it here.] +> +>**Additional Comments** +> +>[At Discretion] + +Keep in mind the list on the addons page is *not* exhaustive. If you came up with a tool that doesn't fit into any of the categories listed, we still want to hear from you! \ No newline at end of file From 94a5c6f5e2605ef9a43b4c7345e7f39f1d28a0af Mon Sep 17 00:00:00 2001 From: EncodedVenom Date: Sat, 17 May 2025 13:59:35 -0400 Subject: [PATCH 3/8] move coverage reports --- {coverage => docs/public/coverage}/ansi.luau.html | 0 {coverage => docs/public/coverage}/entity_visualiser.luau.html | 0 {coverage => docs/public/coverage}/index.html | 0 {coverage => docs/public/coverage}/jecs.luau.html | 0 {coverage => docs/public/coverage}/lifetime_tracker.luau.html | 0 {coverage => docs/public/coverage}/testkit.luau.html | 0 {coverage => docs/public/coverage}/tests.luau.html | 0 7 files changed, 0 insertions(+), 0 deletions(-) rename {coverage => docs/public/coverage}/ansi.luau.html (100%) rename {coverage => docs/public/coverage}/entity_visualiser.luau.html (100%) rename {coverage => docs/public/coverage}/index.html (100%) rename {coverage => docs/public/coverage}/jecs.luau.html (100%) rename {coverage => docs/public/coverage}/lifetime_tracker.luau.html (100%) rename {coverage => docs/public/coverage}/testkit.luau.html (100%) rename {coverage => docs/public/coverage}/tests.luau.html (100%) diff --git a/coverage/ansi.luau.html b/docs/public/coverage/ansi.luau.html similarity index 100% rename from coverage/ansi.luau.html rename to docs/public/coverage/ansi.luau.html diff --git a/coverage/entity_visualiser.luau.html b/docs/public/coverage/entity_visualiser.luau.html similarity index 100% rename from coverage/entity_visualiser.luau.html rename to docs/public/coverage/entity_visualiser.luau.html diff --git a/coverage/index.html b/docs/public/coverage/index.html similarity index 100% rename from coverage/index.html rename to docs/public/coverage/index.html diff --git a/coverage/jecs.luau.html b/docs/public/coverage/jecs.luau.html similarity index 100% rename from coverage/jecs.luau.html rename to docs/public/coverage/jecs.luau.html diff --git a/coverage/lifetime_tracker.luau.html b/docs/public/coverage/lifetime_tracker.luau.html similarity index 100% rename from coverage/lifetime_tracker.luau.html rename to docs/public/coverage/lifetime_tracker.luau.html diff --git a/coverage/testkit.luau.html b/docs/public/coverage/testkit.luau.html similarity index 100% rename from coverage/testkit.luau.html rename to docs/public/coverage/testkit.luau.html diff --git a/coverage/tests.luau.html b/docs/public/coverage/tests.luau.html similarity index 100% rename from coverage/tests.luau.html rename to docs/public/coverage/tests.luau.html From 39946609c7e07cda271307469102b6375440e6a7 Mon Sep 17 00:00:00 2001 From: EncodedVenom Date: Sat, 17 May 2025 13:59:58 -0400 Subject: [PATCH 4/8] use better link for addons --- docs/learn/concepts/addons.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/learn/concepts/addons.md b/docs/learn/concepts/addons.md index 769baa9..8274c2f 100644 --- a/docs/learn/concepts/addons.md +++ b/docs/learn/concepts/addons.md @@ -1,6 +1,6 @@ # Addons -A collection of third-party jecs addons made by the community. If you would like to share what you're working on, [submit a pull request](https://github.com/Ukendio/jecs)! +A collection of third-party jecs addons made by the community. If you would like to share what you're working on, [submit a pull request](/contributing/pull-requests#addons)! # Development tools From 296fe80987075625fb519d39601d6d89fe1c4753 Mon Sep 17 00:00:00 2001 From: EncodedVenom Date: Sat, 17 May 2025 14:00:08 -0400 Subject: [PATCH 5/8] code coverage page --- docs/contributing/coverage.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 docs/contributing/coverage.md diff --git a/docs/contributing/coverage.md b/docs/contributing/coverage.md new file mode 100644 index 0000000..ea56ccb --- /dev/null +++ b/docs/contributing/coverage.md @@ -0,0 +1,17 @@ +# Code Coverage Reports + +All of the code coverage reports can be found here: + +[Overview](/jecs/coverage/index.html){target="_self"} + +[jecs.luau](/jecs/coverage/jecs.luau.html){target="_self"} + +[ANSI](/jecs/coverage/ansi.luau.html){target="_self"} + +[Entity Visualiser](/jecs/coverage/entity_visualiser.luau.html){target="_self"} + +[Lifetime Tracker](/jecs/coverage/lifetime_tracker.luau.html){target="_self"} + +[Testkit](/jecs/coverage/testkit.luau.html){target="_self"} + +[Tests](/jecs/coverage/tests.luau.html){target="_self"} \ No newline at end of file From 3866e1413fb16d8df214b239a66b9b8eedf0ef5d Mon Sep 17 00:00:00 2001 From: EncodedVenom Date: Sat, 17 May 2025 14:00:30 -0400 Subject: [PATCH 6/8] make Introduction sidebar visible on all pages and add a few relevant sections --- docs/.vitepress/config.mts | 37 +++++++++++++++++++++++++++++++------ 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index 537cf76..eb64153 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -15,6 +15,13 @@ export default defineConfig({ sidebar: { "/api/": [ + { + text: "Introduction", + items: [ + { text: "Getting Started", link: "/learn/overview/get-started" }, + { text: "First Jecs Project", link: "/learn/overview/first-jecs-project" }, + ] + }, { text: "API reference", items: [ @@ -22,7 +29,7 @@ export default defineConfig({ { text: "World", link: "/api/world" }, { text: "Query", link: "/api/query" }, ], - }, + } ], "/learn/": [ { @@ -43,17 +50,35 @@ export default defineConfig({ ], }, { - text: "FAQ", - items: [{ text: "How can I contribute?", link: "/learn/faq/contributing" }], + text: "API Reference", + items: [ + { text: "jecs", link: "/api/jecs"}, + { text: "World", link: "/api/world"}, + { text: "Query", link: "/api/query"} + ] }, + { + text: "Contribute", + items: [ + { text: "How Can I Contribute?", link: "/contributing/guidelines" } + ] + } ], "/contributing/": [ + { + text: "Introduction", + items: [ + { text: "Getting Started", link: "/learn/overview/get-started" }, + { text: "First Jecs Project", link: "/learn/overview/first-jecs-project" }, + ], + }, { text: "Contributing", items: [ - { text: "Contribution Guidelines", link: "/learn/contributing/guidelines" }, - { text: "Submitting Issues", link: "/learn/contributing/issues" }, - { text: "Submitting Pull Requests", link: "/learn/contributing/pull-requests" }, + { text: "Contribution Guidelines", link: "/contributing/guidelines" }, + { text: "Submitting Issues", link: "/contributing/issues" }, + { text: "Submitting Pull Requests", link: "/contributing/pull-requests" }, + { text: "Code Coverage", link: "/contributing/coverage" }, ], }, ], From 27a00280fd56e7e8bf61ecf0ea9b3cbfdeb51bd9 Mon Sep 17 00:00:00 2001 From: EncodedVenom Date: Sat, 17 May 2025 19:56:37 -0400 Subject: [PATCH 7/8] The children yearn for the guides --- docs/.vitepress/config.mts | 6 +++++ docs/learn/resources/guides.md | 49 ++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 docs/learn/resources/guides.md diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index eb64153..cd6dccd 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -57,6 +57,12 @@ export default defineConfig({ { text: "Query", link: "/api/query"} ] }, + { + text: "Resources", + items: [ + { text: "Guides", link: "/learn/resources/guides" } + ] + }, { text: "Contribute", items: [ diff --git a/docs/learn/resources/guides.md b/docs/learn/resources/guides.md new file mode 100644 index 0000000..a634c2d --- /dev/null +++ b/docs/learn/resources/guides.md @@ -0,0 +1,49 @@ +# Guides + +ECS is a very foreign concept for those coming from an OOP background. Here's a few resources that the community has compiled about how to use ECS and some of its design choices. + +See something missing? [Let us know.](/contributing/pull-requests) + +## Blogs + +- [An Introduction to ECS for Robloxians - @Ukendio](https://devforum.roblox.com/t/all-about-entity-component-system/1664447) +- [Entities, Components and Systems - Mark Jordan](https://medium.com/ingeniouslysimple/entities-components-and-systems-89c31464240d) +- [Why Vanilla ECS is not enough - Sander Mertens](https://ajmmertens.medium.com/why-vanilla-ecs-is-not-enough-d7ed4e3bebe5) +- [Formalisation of Concepts behind ECS and Entitas - Maxim Zaks](https://medium.com/@icex33/formalisation-of-concepts-behind-ecs-and-entitas-8efe535d9516) +- [Entity Component System and Rendering - Our Machinery](https://ourmachinery.com/post/ecs-and-rendering/) +- [Specs and Legion, two very different approaches to ECS - Cora Sherrat](https://csherratt.github.io/blog/posts/specs-and-legion/) +- [Where are my Entities and Components - Sander Mertens](https://ajmmertens.medium.com/building-an-ecs-1-where-are-my-entities-and-components-63d07c7da742) +- [Archetypes and Vectorization - Sander Mertens](https://medium.com/@ajmmertens/building-an-ecs-2-archetypes-and-vectorization-fe21690805f9) +- [Building Games with Entity Relationships - Sander Mertens](https://ajmmertens.medium.com/building-games-in-ecs-with-entity-relationships-657275ba2c6c) +- [Why it is time to start thinking of games as databases - Sander Mertens](https://ajmmertens.medium.com/why-it-is-time-to-start-thinking-of-games-as-databases-e7971da33ac3) +- [A Roadmap to Entity Relationships - Sander Mertens](https://ajmmertens.medium.com/a-roadmap-to-entity-relationships-5b1d11ebb4eb) +- [Making the most of Entity Identifiers - Sander Mertens](https://ajmmertens.medium.com/doing-a-lot-with-a-little-ecs-identifiers-25a72bd2647) +- [Why Storing State Machines in ECS is a Bad Idea - Sander Mertens](https://ajmmertens.medium.com/why-storing-state-machines-in-ecs-is-a-bad-idea-742de7a18e59) +- [ECS back & forth - Michele Caini](https://skypjack.github.io/2019-02-14-ecs-baf-part-1/) +- [Sparse Set - Geeks for Geeks](https://www.geeksforgeeks.org/sparse-set/) + +## Videos + +- [Taking the Entity-Component-System Architecture Seriously - @alice-i-cecile](https://www.youtube.com/watch?v=VpiprNBEZsk) +- [Overwatch Gameplay Architecture and Netcode - Blizzard, GDC](https://www.youtube.com/watch?v=W3aieHjyNvw) +- [Data-Oriented Design and C++ - Mike Acton, CppCon](https://www.youtube.com/watch?v=rX0ItVEVjHc) +- [Using Rust for Game Development - Catherine West, RustConf](https://www.youtube.com/watch?v=aKLntZcp27M) +- [CPU caches and why you should care - Scott Meyers, NDC](https://vimeo.com/97337258) +- [Building a fast ECS on top of a slow ECS - @UnitOfTime](https://youtu.be/71RSWVyOMEY) +- [Culling the Battlefield: Data Oriented Design in Practice - DICE, GDC](https://www.gdcvault.com/play/1014491/Culling-the-Battlefield-Data-Oriented) +- [Game Engine Entity/Object Systems - Bobby Anguelov](https://www.youtube.com/watch?v=jjEsB611kxs) +- [Understanding Data Oriented Design for Entity Component Systems - Unity GDC](https://www.youtube.com/watch?v=0_Byw9UMn9g) + +## Tutorials + +- [Understanding Data Oriented Design - Unity](https://learn.unity.com/tutorial/part-1-understand-data-oriented-design?courseId=60132919edbc2a56f9d439c3&signup=true&uv=2020.1) + +## Books + +- [Data Oriented Design - Richard Fabian](https://www.dataorienteddesign.com/dodbook/dodmain.html) + +## Other + +- [Interactive app for browsing systems of City Skylines 2 - @Captain-Of-Coit](https://captain-of-coit.github.io/cs2-ecs-explorer/) +- [Awesome Entity Component System (link collection related to ECS) - Jeongseok Lee](https://github.com/jslee02/awesome-entity-component-system) +- [Hibitset - DOCS.RS](https://docs.rs/hibitset/0.6.3/hibitset/) From 8b8d5f715ed1db0a8af6195b63a5b1e83a8099a2 Mon Sep 17 00:00:00 2001 From: EncodedVenom Date: Sat, 17 May 2025 19:59:09 -0400 Subject: [PATCH 8/8] ECS is foreign to everyone --- docs/learn/resources/guides.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/learn/resources/guides.md b/docs/learn/resources/guides.md index a634c2d..3b6a30e 100644 --- a/docs/learn/resources/guides.md +++ b/docs/learn/resources/guides.md @@ -1,6 +1,6 @@ # Guides -ECS is a very foreign concept for those coming from an OOP background. Here's a few resources that the community has compiled about how to use ECS and some of its design choices. +ECS is a very foreign concept. Here's a few resources that the community has compiled about how to use ECS and some of its design choices. See something missing? [Let us know.](/contributing/pull-requests)