2 * coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
3 * Understanding is not required. Only obedience.
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 module gaem
.runner
.objects
is aliced
;
21 import gaem
.runner
.strpool
;
22 import gaem
.runner
.value
;
23 import gaem
.runner
.sprites
;
26 // ////////////////////////////////////////////////////////////////////////// //
27 // each instance is registered in all it's parent objects instance lists
29 private __gshared
uint[string
] fields
;
32 package(gaem
.runner
) short allocateFieldId (string name
) {
33 assert(name
.length
> 0);
34 if (auto fpi
= name
in fields
) return cast(short)*fpi
;
35 auto fid
= cast(uint)fields
.length
;
36 if (fid
> short.max
) assert(0, "too many fields");
38 return cast(short)fid
;
42 private enum PredefinedFields
= [
43 "object_index", // int
45 "sprite_index", // int
46 "sprite_width", // int
47 "sprite_height", // int
48 "sprite_xoffset", // int
49 "sprite_yoffset", // int
50 "image_index", // Real
51 "image_speed", // Real
52 "image_xscale", // Real
53 "image_yscale", // Real
54 "image_angle", // Real
55 "image_alpha", // Real
70 "gravity_direction", // Real
94 // create `fi_xxx` variables
97 foreach (string name
; PredefinedFields
) res
~= "private __gshared uint fi_"~name
~";\n";
101 // create predefined fields
102 shared static this () {
105 foreach (string name
; PredefinedFields
) res
~= "fi_"~name
~" = allocateFieldId(`"~name
~"`);\n";
111 package(gaem
.runner
) uint fieldCount () { pragma(inline
, true); return cast(uint)fields
.length
; }
114 // ////////////////////////////////////////////////////////////////////////// //
115 // game object (instance template)
116 final class ObjectTpl
{
118 ObjectTpl parent
; // 0: no parent -- root object
119 uint idx
; // globally unique index (should never be zero)
127 uint[][GMEvent
.Type
.max
+1] events
; //TODO
129 InstList ilist
; // all instances with this object in parent chain
132 private __gshared ObjectTpl
[] objects
; // 0 is unused
133 private __gshared ObjectTpl
[string
] objByNameMap
;
134 shared static this () { objects
.length
= 1; }
136 enum ObjIdAll
= -666;
139 // ////////////////////////////////////////////////////////////////////////// //
140 public void createObjects (Gmk gmk
) {
141 gmk
.forEachObject((o
) {
143 if (o
.name
.length
== 0) assert(0, "can't register nameless object");
144 if (o
.name
in objByNameMap
) assert(0, "object '"~o
.name
~"' already registered");
145 auto tpl
= new ObjectTpl();
147 tpl
.idx
= (o
.parentobjidx
>= 0 ? o
.parentobjidx
: uint.max
); // temporarily abuse this field
148 tpl
.sprite_index
= o
.spridx
;
149 tpl
.mask_index
= o
.maskspridx
;
151 tpl
.visible
= o
.visible
;
153 tpl
.persistent
= o
.persistent
;
154 objByNameMap
[o
.name
] = tpl
;
160 foreach (immutable idx
, ObjectTpl tpl
; objects
[1..$]) {
161 if (tpl
.idx
!= uint.max
) {
162 auto po
= gmk
.objByNum(tpl
.idx
);
163 if (po
is null) assert(0, "invalid parent for object '"~tpl
.name
~"'");
164 if (auto px
= po
.name
in objByNameMap
) tpl
.parent
= *px
; else assert(0, "wtf?!");
166 tpl
.idx
= cast(uint)idx
; // fix index
172 uint objectByName (const(char)[] name
) {
173 if (auto tpp
= name
in objByNameMap
) return (*tpp
).idx
;
178 bool validObjectId (uint id
) { pragma(inline
, true); return (id
> 0 && id
< objects
.length
); }
181 // ////////////////////////////////////////////////////////////////////////// //
182 // circular double-linked list
187 void append (InstProxy o
) {
188 if (o
is null) return;
189 assert(o
.prev
is null);
190 assert(o
.next
is null);
191 // append to circular list
196 } else if (head
.next
is head
) {
197 // list has only one item
198 o
.prev
= o
.next
= head
;
199 head
.prev
= head
.next
= o
;
201 // list has more than one item
202 auto tail
= head
.prev
;
203 o
.prev
= tail
; // previous is list tail
204 o
.next
= head
; // next is list head
211 void remove (InstProxy o
) {
212 if (o
is null || o
.prev
is null) return;
213 assert(head
!is null);
214 // remove from circular list
215 if (head
.prev
is head
) {
220 // list has more than one item
221 if (head
is o
) head
= head
.next
; // deleting head item, move head
222 o
.prev
.next
= o
.next
;
223 o
.next
.prev
= o
.prev
;
225 o
.prev
= o
.next
= null;
231 // proxy for instance lists
232 private final class InstProxy
{
234 InstProxy prev
, next
;
237 this (Instance aself
, ObjectTpl aparent
=null) {
240 if (aself
!is null) {
241 if (aparent
!is null) aparent
.ilist
.append(this); else iall
.append(this);
245 void removeFromLists () {
247 if (parent
!is null) parent
.ilist
.remove(this); else iall
.remove(this);
253 // ////////////////////////////////////////////////////////////////////////// //
254 private __gshared InstList iall
; // all created instances
255 // single-linked list of all dead instances
256 private __gshared Instance deadList
;
260 // ////////////////////////////////////////////////////////////////////////// //
261 final class Instance
{
263 enum IdStart
= 100000;
264 __gshared
uint nextid
= IdStart
;
265 __gshared Instance
[uint] instById
;
266 __gshared
ulong curStep
= 0; // used for `with` management
269 Instance deadNext
; // in `deadList`
277 Real
[][] farrays
; // arrays for fields
278 ulong stepMark
; // used in `with` management
280 this (ObjectTpl aparent
) {
282 proxies
~= new InstProxy(this); // add to list of all instances
284 fields
.length
= fieldCount
;
286 // copy initial fields from parent object
287 if (aparent
!is null) {
288 fields
.ptr
[fi_id
] = Value(mId
);
289 fields
.ptr
[fi_sprite_index
] = Value(aparent
.sprite_index
);
290 fields
.ptr
[fi_mask_index
] = Value(aparent
.mask_index
);
291 fields
.ptr
[fi_solid
] = Value(aparent
.solid
);
292 fields
.ptr
[fi_visible
] = Value(aparent
.visible
);
293 fields
.ptr
[fi_depth
] = Value(aparent
.depth
);
294 fields
.ptr
[fi_persistent
] = Value(aparent
.persistent
);
296 // add to parents' lists
297 while (aparent
!is null) {
298 proxies
~= new InstProxy(this, aparent
);
299 aparent
= aparent
.parent
;
305 @property uint id () const pure nothrow @safe @nogc { return mId
; }
308 Real
get (uint fieldindex
) {
309 pragma(inline
, true);
310 return (fieldindex
< fields
.length ? fields
.ptr
[fieldindex
] : Value());
313 Real
get (uint fieldindex
, uint i0
, uint i1
=0) {
314 if (i0
>= 32000 || i1
>= 32000) return Value(); // out of range
315 if (fieldindex
>= farrays
.length
) return Value();
317 auto v
= farrays
.ptr
[fieldindex
];
318 return (i0
< v
.length ? v
.ptr
[i0
] : Value());
321 void set (Real val
, uint fieldindex
) {
322 if (fieldindex
< fields
.length
) fields
.ptr
[fieldindex
] = val
;
325 void set (Real val
, uint fieldindex
, uint i0
, uint i1
=0) {
326 if (i0
>= 32000 || i1
>= 32000) return; // out of range
327 if (fieldindex
>= fields
.length
) return;
329 if (farrays
.length
< fieldindex
+1) farrays
.length
= fieldindex
+1;
330 if (farrays
.ptr
[fieldindex
].length
< i0
+1) farrays
.ptr
[fieldindex
].length
= i0
+1;
331 farrays
.ptr
[fieldindex
].ptr
[i0
] = val
;
334 bool isInstanceOf (int objid
) {
335 if (objid
== ObjIdAll
) return true;
336 if (objid
<= 0 || objid
>= objects
.length || mParent
is null) return false;
337 for (ObjectTpl p
= objects
.ptr
[objid
]; p
!is null; p
= p
.parent
) if (mParent
is p
) return true;
342 if (deadNext
is null) {
346 debug(objlist
) { import core
.stdc
.stdio
: printf
; printf("* instance %u of type '%.*s' marked as dead\n", mId
, cast(uint)mParent
.name
.length
, mParent
.name
.ptr
); }
352 void advanceFrame () {
353 pragma(inline
, true);
357 // ////////////////////////////////////////////////////////////////////// //
360 private static struct Iterator
{
361 InstProxy head
; // starting instance
362 InstProxy cur
; // current instance
363 Instance si
; // instance for single-instance iterator
367 private __gshared Iterator
[] iters
;
368 private __gshared
uint itersUsed
= 1;
370 shared static this () {
371 iters
.length
= 1024; // arbitrary number
374 private uint newIId () {
375 pragma(inline
, true);
376 auto iid
= itersUsed
++;
377 if (iid
== iters
.length
) iters
.length
+= 1024;
381 // create new iterator, return iid or 0
382 uint newIterator (int objid
, uint aOldSelf
) {
383 if (itersUsed
>= short.max
) assert(0, "too many iterators");
385 if (objid
>= IdStart
) {
386 if (auto i
= cast(uint)objid
in instById
) {
388 iters
.ptr
[iid
].oldSelf
= aOldSelf
;
389 iters
.ptr
[iid
].si
= *i
;
395 if (objid
== ObjIdAll
) {
396 if (iall
.head
is null) return 0; // no instances yet
398 with (iters
.ptr
[iid
]) { step
= curStep
++; head
= cur
= iall
.head
; oldSelf
= aOldSelf
; }
402 if (objid
<= 0) return 0;
403 if (objid
< objects
.length
) {
405 if (auto proxy
= objects
.ptr
[objid
].ilist
.head
) {
407 with (iters
.ptr
[iid
]) { step
= curStep
++; head
= cur
= proxy
; oldSelf
= aOldSelf
; }
415 // returns current object or 0 on completion, move iterator to next object
416 uint iteratorNext (uint iid
) {
417 if (iid
== 0 || iid
>= itersUsed
) return 0;
418 auto it
= &iters
.ptr
[iid
];
419 if (it
.head
is null) {
420 if (it
.si
is null) return 0; // dead iterator
421 auto res
= (!it
.si
.mDead ? it
.si
.mId
: 0);
427 auto ri
= it
.cur
.self
;
428 if ((it
.cur
= it
.cur
.next
) is it
.head
) it
.head
= it
.cur
= null;
429 if (!ri
.mDead
&& ri
.stepMark
<= it
.step
) return ri
.mId
; // good instance
430 // bad instance (either dead, or newborn) move on
431 } while (it
.cur
!is null);
432 return 0; // no more instances
435 uint iteratorOldSelf (uint iid
) {
436 pragma(inline
, true);
437 return (iid
== 0 || iid
>= itersUsed ?
0 : iters
.ptr
[iid
].oldSelf
);
440 void freeAllIterators () {
442 foreach (ref it
; iters
[1..itersUsed
]) { it
.head
= it
.cur
= null; it
.si
= null; }
448 // return `true` from delegate to stop
449 // will skip dead instances
450 Instance
forEach (int objid
, scope bool delegate (Instance inst
) dg
) {
453 if (objid
>= IdStart
) {
454 if (auto i
= cast(uint)objid
in instById
) return (dg(*i
) ?
*i
: null);
459 if (objid
== ObjIdAll
) {
460 head
= cur
= iall
.head
;
461 } else if (objid
> 0 && objid
< objects
.length
) {
462 if ((head
= cur
= objects
.ptr
[objid
].ilist
.head
) is null) return null;
468 if (!cur
.self
.mDead
&& dg(cur
.self
)) return cur
.self
;
469 if ((cur
= cur
.next
) is head
) break;
474 Instance
firstInstanceOf (int id
) {
476 if (auto pid
= cast(uint)id
in instById
) return *pid
;
477 } else if (id
> 0 && id
< objects
.length
) {
478 if (auto px
= objects
.ptr
[id
].ilist
.head
) return px
.self
;
484 void freeDeadObjects () {
485 while (deadList
!is null) {
486 assert(deadList
.mDead
);
487 foreach (InstProxy px
; deadList
.proxies
) px
.removeFromLists();
488 // remove from alive instances hash
489 // do it here instead of `kill()`, so stored objids will still work until script end
490 instById
.remove(deadList
.mId
);
491 deadList
= deadList
.deadNext
;
496 // this should be called when top-level script execution is complete
497 void scriptComplete () {