Skip to content

Commit d4a7f1d

Browse files
committed
v0.10
1 parent 33f7c08 commit d4a7f1d

File tree

8 files changed

+304
-66
lines changed

8 files changed

+304
-66
lines changed

how_to/030_archetypes.luau

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,3 +82,23 @@ world:set(e1, Position, vector.create(10, 20, 30))
8282
- [Position, Velocity] -> [Position] (for removing Velocity)
8383
8484
]]
85+
86+
local pair = jecs.pair
87+
88+
local Likes = world:component()
89+
local alice = world:entity()
90+
local bob = world:entity()
91+
local charlie = world:entity()
92+
93+
local e2 = world:entity()
94+
world:add(e2, pair(Likes, alice)) -- Creates archetype [pair(Likes, alice)]
95+
96+
local e3 = world:entity()
97+
world:add(e3, pair(Likes, bob)) -- Creates archetype [pair(Likes, bob)]
98+
99+
local e4 = world:entity()
100+
world:add(e3, pair(Likes, charlie)) -- Creates archetype [pair(Likes, charlie)]
101+
world:add(e3, pair(Likes, bob)) -- Creates archetype [pair(Likes, bob), pair(Likes, charlie)]
102+
103+
-- Each different target creates a new archetype, leading to fragmentation
104+
-- This is why relationships can increase archetype count significantly

how_to/040_fragmentation.luau

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,3 @@
1-
local jecs = require("@jecs")
2-
local pair = jecs.pair
3-
local world = jecs.world()
4-
5-
local Likes = world:component()
6-
71
--[[
82
Fragmentation is a property of archetype-based ECS implementations where entities
93
are spread out over more archetypes as the number of different component combinations
@@ -30,19 +24,3 @@ local Likes = world:component()
3024
pair(jecs.Wildcard, Apples) indices. For this reason, creating new archetypes
3125
with relationships has a higher overhead than an archetype without relationships.
3226
]]
33-
34-
local alice = world:entity()
35-
local bob = world:entity()
36-
local charlie = world:entity()
37-
38-
local e1 = world:entity()
39-
world:add(e1, pair(Likes, alice)) -- Creates archetype [pair(Likes, alice)]
40-
41-
local e2 = world:entity()
42-
world:add(e2, pair(Likes, bob)) -- Creates archetype [pair(Likes, bob)]
43-
44-
local e3 = world:entity()
45-
world:add(e3, pair(Likes, charlie)) -- Creates archetype [pair(Likes, charlie)]
46-
47-
-- Each different target creates a new archetype, leading to fragmentation
48-
-- This is why relationships can increase archetype count significantly

how_to/110_hooks.luau

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ end)
7070
local Health = world:component()
7171
local Dead = world:component()
7272

73-
world:set(Health, jecs.OnRemove, function(entity, id, delete)
73+
world:set(Health, jecs.OnRemove, function(entity: jecs.Entity, id, delete)
7474
if delete then
7575
-- Entity is being deleted, don't try to clean up
7676
return

how_to/111_signals.luau

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
--[[
2+
Signals let you subscribe to component add, change, and remove events with
3+
multiple listeners per component. Unlike hooks (see 110_hooks.luau), which
4+
allow only one OnAdd, OnChange, and OnRemove per component, signals support
5+
any number of subscribers and each subscription returns an unsubscribe
6+
function so you can clean up when you no longer need to listen.
7+
8+
Use signals when you need several independent systems to react to the same
9+
component lifecycle events, or when you want to subscribe and unsubscribe
10+
dynamically (e.g. a UI that only cares while it's mounted).
11+
]]
12+
13+
local jecs = require("@jecs")
14+
local world = jecs.world()
15+
16+
local Position = world:component() :: jecs.Id<{ x: number, y: number }>
17+
18+
--[[
19+
world:added(component, fn)
20+
21+
Subscribe to "component added" events. Your callback is invoked with:
22+
(entity, id, value, oldarchetype) whenever the component is added to an entity.
23+
24+
Returns a function; call it to unsubscribe.
25+
]]
26+
27+
local unsub_added = world:added(Position, function(entity, id, value, oldarchetype)
28+
print(`Position added to entity {entity}: ({value.x}, {value.y})`)
29+
end)
30+
31+
--[[
32+
world:changed(component, fn)
33+
34+
Subscribe to "component changed" events. Your callback is invoked with:
35+
(entity, id, value, oldarchetype) whenever the component's value is updated
36+
on an entity (e.g. via world:set).
37+
38+
Returns a function; call it to unsubscribe.
39+
]]
40+
41+
local unsub_changed = world:changed(Position, function(entity, id, value, oldarchetype)
42+
print(`Position changed on entity {entity}: ({value.x}, {value.y})`)
43+
end)
44+
45+
--[[
46+
world:removed(component, fn)
47+
48+
Subscribe to "component removed" events. Your callback is invoked with:
49+
(entity, id, delete?) when the component is removed. The third argument
50+
`delete` is true when the entity is being deleted, false or nil when
51+
only the component was removed (same semantics as OnRemove in 110_hooks).
52+
53+
Returns a function; call it to unsubscribe.
54+
]]
55+
56+
local unsub_removed = world:removed(Position, function(entity, id, delete)
57+
if delete then
58+
print(`Entity {entity} deleted (had Position)`)
59+
else
60+
print(`Position removed from entity {entity}`)
61+
end
62+
end)
63+
64+
local e = world:entity()
65+
world:set(e, Position, { x = 10, y = 20 }) -- added
66+
world:set(e, Position, { x = 30, y = 40 }) -- changed
67+
world:remove(e, Position) -- removed
68+
69+
world:added(Position, function(entity)
70+
print("Second listener: Position added")
71+
end)
72+
73+
world:set(e, Position, { x = 0, y = 0 }) -- Multiple listeners are all invoked
74+
75+
-- Unsubscribe when you no longer need to listen
76+
unsub_added()
77+
unsub_changed()
78+
unsub_removed()
79+
80+
world:set(e, Position, { x = 1, y = 1 })
81+
world:remove(e, Position)

how_to/999_temperance.luau

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
-- These notes are my thoughts jotted down from having experienced these
2+
-- problems myself and gathered insights from many admired individuals such as
3+
-- Sander Mertens, Ryan Fleury, Jonathon Blow, Benjamin Saunders and many more...
4+
5+
--[[
6+
7+
In 1993, the original source code for DOOM was about 50,000 lines of code. And
8+
when your code gets into that neighbourhood, it should provide a large amount of
9+
interesting and novel functionality. If it doesn't, perhaps it is time to ask questions.
10+
11+
Please, try to write code that is small, and that does a lot for its size.
12+
13+
Please, after you finish writing something, ask yourself whether you are
14+
satisfied with how robust it is, and with how much it gets done for how much
15+
code there is.
16+
17+
- Transfer of tacit knowledge is incredibly important. If tacit knowledge
18+
about the code base is lost, ability to work on it at the same level of quality
19+
is lost. Over time code quality will decline as code size grows.
20+
21+
- Tacit knowledge is very hard to recover by looking at a maze of code,
22+
and it takes
23+
24+
- You will often hear that "every semantic distinction deserves its own
25+
component or tag". Sometimes this is correct. A well chosen component boundary
26+
can make queries clear and systems obvious. But sometimes this distinction would
27+
be better served as a field, a bitset, or a local data structure. The
28+
representation should match the problem.
29+
30+
Sub-Essay Here: Code Should Not Try To Meet Every Need Anyone May Ever Have.
31+
32+
Over-generalization leads to bloat and poor functionality. A common failure mode
33+
is writing code "for everyone" while building configuration for every scenario,
34+
abstractions for every future feature, and extension points for every imagined
35+
consumer. It sounds responsible. It usually isn't.
36+
Specialization is good in many cases. Think about the guy with the truck full of
37+
automotive tools, who has a bunch of different wrenches. He doesn't carry one
38+
wrench that transforms into every tool; he carries a few specialized tools that
39+
are reliable and fast to use. One reason we have endless bloat is that we teach
40+
that all code should expand until it meets all needs. This is wrong,
41+
empirically, and we should stop teaching it.
42+
43+
- Relationships are very powerful however, with each pair being an unique
44+
component, it can be an easy way to accidentally increase the number of archetypes which can cause
45+
higher churn in systems.
46+
47+
- A hook is not a replacement for systems. They are for enforcing invariants when data changes during different lifecycles.
48+
When gameplay logic that should run predictably each frame is instead scattered
49+
across hooks, behaviour becomes implicit when it is triggered indirectly through
50+
a cascade of changes that, logic split across many small
51+
callbacks that fire in surprising order. Which also gets harder to reason about
52+
and optimize.
53+
54+
]]

package.json

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@rbxts/jecs",
3-
"version": "0.9.0",
3+
"version": "0.10.0",
44
"description": "Stupidly fast Entity Component System",
55
"main": "src/jecs.luau",
66
"repository": {
@@ -37,10 +37,5 @@
3737
"roblox-ts": "^3.0.0",
3838
"typescript": "^5.4.2",
3939
"vitepress": "^1.3.0"
40-
},
41-
"scripts": {
42-
"docs:dev": "vitepress dev docs",
43-
"docs:build": "vitepress build docs",
44-
"docs:preview": "vitepress preview docs"
4540
}
4641
}

0 commit comments

Comments
 (0)