/
world.clj
335 lines (261 loc) · 8.25 KB
/
world.clj
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
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
(ns clecs.world
"Protocols that define clecs API.
#### Definitions
World
: Worlds are top level containers. Entities,
components are stored in worlds and systems
are run within the context of worlds.
Entity
: Objects in the world. Entities are merely
identifiers, they do not store any information.
This is an important difference from object
oriented data structures, where all information
related to an object is stored with the object.
Clecs refers to entities as `entity-id` or
`eid`. An entity-id can be an integer or a
UUID or any other type depending on the
world implementation.
Component
: Components encode a single aspect about an
entity. Entities can have any number of
components and components can be added or
removed from an entity during its lifetime.
Therefore there is not necessarily a static
set components for a set of entities. This
is another difference from the object oriented
approach which enforces a static template of
aspects for each type of entity.
System
: Systems are either maps (new style) or (old style)
callables that define operations
over the world. Each system should ideally
deal with a unique aspect of the application.
Systems may primarily deal with a single
component but it is not a requirement.
Query
: There are two kinds of queries in clecs:
1. Queries for entities associated with certain
components.
1. Queries of a certain entity for its individual
components.
Queries can be only be run by systems.
#### Relationships Between Concepts
+++++++++++++++++++++++++++++++++++++
| |
| Main +++++++++++++++++++++++++ |
| Loop | | |
| | World | |
| | | |
| | +++++++++++++++++++ | |
| | | | | |
| | | Components | | |
| | | | | |
| | +++++++++++++++++++ | |
| | | | | |
| | | Systems | | |
| | | | | |
| | | +++++++++++++ | | |
| | | | | | | |
| | | | Queries | | | |
| | | | | | | |
| | | +++++++++++++ | | |
| | | | | |
| | +++++++++++++++++++ | |
| | | |
| +++++++++++++++++++++++++ |
| |
+++++++++++++++++++++++++++++++++++++
"
(:require [clecs.component :refer [validate]]
[clecs.world.validate :refer [validate-world]]))
(defprotocol IEditableWorld
(-set-component
[this eid cname cdata]
"Sets a component without validating.
Use [[set-component]] instead of calling this directly.
#### Parameters:
eid
: Entity id.
cname
: Component name.
cdata
: Component data as a map.
")
(add-entity
[this]
"Create a new entity in the world and return its id.")
(remove-component
[this eid cname]
"Remove the component of type `cname` that is associated
with `eid` and return `nil`.
This method is a no-op if there is no relevant component.
#### Parameters:
eid
: Entity id.
cname
: Component name.
")
(remove-entity
[this eid]
"Delete the entity with `eid`, all components
associated with it and return `nil`.
This method is a no-op if there is no relevant entity.
#### Parameters:
eid
: Entity id.
"))
(defprotocol IQueryableWorld
(-component
[this cname]
"Return component definition for `cname` or `nil`
if none found.
Use [[component]] to query a component for an entity.
#### Parameters:
cname
: Component name.
")
(component
[this eid cname]
"Return the component of type `cname` associated with
`eid`, or `nil` if none found.
#### Parameters:
eid
: Entity id.
cname
: Component name.
")
(query
[this q]
"Return a sequence of entity id's using `q` as
filter criteria.
#### Parameters:
q
: A query object. See [[clecs.query]] for more
info.
"))
(defprotocol IWorld
(-run
[this reads writes f dt]
"Run `f` passing it an editable world and `dt`. Return world.
Use [[process!]] instead of calling `-run` directly.
#### Parameters:
reads
: Components that are readable for `f`.
writes
: Components that are readable & writable for `f`.
f
: A function that takes an editable world and
a time increment as parameters.
dt
: Time passed since this function is last run.
")
(process!
[this dt]
"Run systems using `dt` as time increment.
This is the function that will be called in
the main loop.
#### Parameters:
dt
: Time passed since process! was called last
time. This value is passed to the systems.
It is recommended to use miliseconds as
unit.
"))
(defprotocol IWorldFactory
(-supported-types
[this]
"Component parameter types supported by this backend.")
(-world
[this components systems extra-config]
"Creates a world.
#### Parameters:
components
: A map of component names to components.
systems
: A map of system names to systems.
extra-config
: A map containing other elements passed
to [[world]]. Some backends may require
certains parameters other than components
and systems.
Use [[world]] instead of calling this directly."))
(defn set-component
"Set `eid`'s `cname` component as `cdata` and return
`nil`.
#### Parameters:
world
: World.
eid
: Entity id.
cname
: Component type.
cdata
: Component data as a map."
[world eid cname cdata]
(if-some [c (-component world cname)]
(do
(validate c cdata)
(-set-component world eid cname cdata)
nil)
(throw (RuntimeException. (str "Unknown component " cname)))))
(defn world
"Creates a world.
#### Parameters:
world-factory
: A instance of [[IWorldFactory]].
params
: A map of world parameters. Keys recognized by
all backends are described below:
:components
: A sequence of [[clecs.component/component]]
instances. At least one component must be
specified.
:initializer
: A function that takes an editable world and
run as soon as the world is created.
:systems
: A sequence of [[clecs.system/system]]
instances. At least one system must be
specified.
Any other keys will be passed to the factory.
See the backend's documentation for recognized
parameters.
#### Examples:
(clecs.world/world atom-world-factory
{:components [(component ...)
(component ...)
(component ...)
...]
:initializer (fn [w] ...)
:systems [(system ...)
(system ...)
...]})
"
[world-factory
{components :components
initializer :initializer
systems :systems :as params}]
(validate-world components
systems
(-supported-types world-factory))
(let [systems-map (->> systems
(map (juxt :name identity))
(into {}))
components-map (->> components
(map (juxt :name identity))
(into {}))
extra-config (dissoc params
:components
:initializer
:systems)
w (-world world-factory
components-map
systems-map
extra-config)]
(if initializer
(-run w
components-map
components-map
(fn [w _] (initializer w))
nil)
w)))