-
-
Notifications
You must be signed in to change notification settings - Fork 18
Expand file tree
/
Copy pathComponent.luau
More file actions
197 lines (155 loc) · 5.76 KB
/
Component.luau
File metadata and controls
197 lines (155 loc) · 5.76 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
local merge = require(script.Parent.immutable).merge
--[=[
@class Component
A component is a named piece of data that exists on an entity.
Components are created and removed in the [World](/api/World).
In the docs, the terms "Component" and "ComponentInstance" are used:
- **"Component"** refers to the base class of a specific type of component you've created.
This is what [`Matter.component`](/api/Matter#component) returns.
- **"Component Instance"** refers to an actual piece of data that can exist on an entity.
The metatable of a component instance table is its respective Component table.
Component instances are *plain-old data*: they do not contain behaviors or methods.
Since component instances are immutable, one helper function exists on all component instances, `patch`,
which allows reusing data from an existing component instance to make up for the ergonomic loss of mutations.
]=]
--[=[
@within Component
@type ComponentInstance {}
The `ComponentInstance` type refers to an actual piece of data that can exist on an entity.
The metatable of the component instance table is set to its particular Component table.
A component instance can be created by calling the Component table:
```lua
-- Component:
local MyComponent = Matter.component("My component")
-- component instance:
local myComponentInstance = MyComponent({
some = "data"
})
print(getmetatable(myComponentInstance) == MyComponent) --> true
```
]=]
-- This is a special value we set inside the component's metatable that will allow us to detect when
-- a Component is accidentally inserted as a Component Instance.
-- It should not be accessible through indexing into a component instance directly.
local DIAGNOSTIC_COMPONENT_MARKER = table.freeze({})
-- This tells the World whether the component should be unwrapped on insertion.
local PRIMITIVE_MARKER = table.freeze({})
local lastId = 0
local function new(name: string, defaultData)
name = name or debug.info(2, "s") .. "@" .. debug.info(2, "l")
local ComponentInstance = {}
ComponentInstance.__index = ComponentInstance
function ComponentInstance.new(data)
-- If we aren't passed data, then we use the default data.
-- If no default data is provided, we want to default a table.
data = if data == nil then defaultData or {} else data
local component = getmetatable(ComponentInstance :: any)
if typeof(data) == "table" then
if defaultData then
data = merge(defaultData, data)
end
return table.freeze(setmetatable(data, ComponentInstance))
else
component[PRIMITIVE_MARKER] = true
return table.freeze(setmetatable({ data = data, [PRIMITIVE_MARKER] = true }, ComponentInstance))
end
end
--[=[
@within Component
```lua
for id, target in world:query(Target) do
if shouldChangeTarget(target) then
world:insert(id, target:patch({ -- modify the existing component
currentTarget = getNewTarget()
}))
end
end
```
A utility function used to immutably modify an existing component instance. Key/value pairs from the passed table
will override those of the existing component instance.
As all components are immutable and frozen, it is not possible to modify the existing component directly.
You can use the `Matter.None` constant to remove a value from the component instance:
```lua
target:patch({
currentTarget = Matter.None -- sets currentTarget to nil
})
```
@param partialNewData {} -- The table to be merged with the existing component data.
@return ComponentInstance -- A copy of the component instance with values from `partialNewData` overriding existing values.
]=]
function ComponentInstance:patch(partialNewData)
return getmetatable(self).new(merge(self, partialNewData))
end
lastId += 1
local id = lastId
setmetatable(ComponentInstance, {
__call = function(_, ...)
return ComponentInstance.new(...)
end,
__tostring = function()
return name
end,
__len = function()
return id
end,
[DIAGNOSTIC_COMPONENT_MARKER] = true,
})
return ComponentInstance
end
local function assertValidType(value, position)
if typeof(value) ~= "table" then
error(string.format("Component #%d is invalid: not a table", position), 3)
end
local metatable = getmetatable(value)
if metatable == nil then
error(string.format("Component #%d is invalid: has no metatable", position), 3)
end
end
local function assertValidComponent(value, position)
assertValidType(value, position)
local metatable = getmetatable(value)
if getmetatable(metatable) ~= nil and getmetatable(metatable)[DIAGNOSTIC_COMPONENT_MARKER] then
error(
string.format(
"Component #%d is invalid: Component Instance %s was passed instead of the Component itself!",
position,
tostring(metatable)
),
3
)
end
end
local function assertValidComponentInstance(value, position)
assertValidType(value, position)
if getmetatable(value)[DIAGNOSTIC_COMPONENT_MARKER] ~= nil then
error(
string.format(
"Component #%d is invalid: passed a Component instead of a Component instance; "
.. "did you forget to call it as a function?",
position
),
3
)
end
end
local function assertValidComponentInstances(componentInstances)
for position, componentInstance in componentInstances do
assertValidComponentInstance(componentInstance, position)
end
end
local function assertComponentArgsProvided(...)
if not (...) then
error(`No components passed to world:{debug.info(3, "n")}, at least one component is required`, 2)
end
end
local function isPrimitive(componentInstance)
return componentInstance[PRIMITIVE_MARKER] ~= nil
end
return {
new = new,
assertValidComponentInstance = assertValidComponentInstance,
assertValidComponentInstances = assertValidComponentInstances,
assertComponentArgsProvided = assertComponentArgsProvided,
assertValidComponent = assertValidComponent,
isPrimitive = isPrimitive,
}