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, version 3 of the License ONLY.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 module gaem
.runner
.objects
is aliced
;
20 import gaem
.runner
.strpool
;
21 import gaem
.runner
.value
;
22 import gaem
.runner
.sprites
;
25 // ////////////////////////////////////////////////////////////////////////// //
26 // each instance is registered in all it's parent objects instance lists
28 private __gshared
uint[string
] fields
;
31 package(gaem
.runner
) short allocateFieldId (string name
) {
32 assert(name
.length
> 0);
33 if (auto fpi
= name
in fields
) return cast(short)*fpi
;
34 auto fid
= cast(uint)fields
.length
;
35 if (fid
> short.max
) assert(0, "too many fields");
37 return cast(short)fid
;
41 private enum PredefinedFields
= [
42 "object_index", // int
44 "sprite_index", // int
45 "sprite_width", // int
46 "sprite_height", // int
47 "sprite_xoffset", // int
48 "sprite_yoffset", // int
49 "image_index", // Real
50 "image_speed", // Real
51 "image_xscale", // Real
52 "image_yscale", // Real
53 "image_angle", // Real
54 "image_alpha", // Real
69 "gravity_direction", // Real
93 // create `fi_xxx` variables
96 foreach (string name
; PredefinedFields
) res
~= "private __gshared uint fi_"~name
~";\n";
100 // create predefined fields
101 shared static this () {
104 foreach (string name
; PredefinedFields
) res
~= "fi_"~name
~" = allocateFieldId(`"~name
~"`);\n";
110 package(gaem
.runner
) uint fieldCount () { pragma(inline
, true); return cast(uint)fields
.length
; }
113 // ////////////////////////////////////////////////////////////////////////// //
114 // game object (instance template)
115 final class ObjectTpl
{
117 ObjectTpl parent
; // 0: no parent -- root object
118 uint idx
; // globally unique index (should never be zero)
126 uint[][GMEvent
.Type
.max
+1] events
; //TODO
128 InstList ilist
; // all instances with this object in parent chain
131 private __gshared ObjectTpl
[] objects
; // 0 is unused
132 private __gshared ObjectTpl
[string
] objByNameMap
;
133 shared static this () { objects
.length
= 1; }
135 enum ObjIdAll
= -666;
138 // ////////////////////////////////////////////////////////////////////////// //
139 public void createObjects (Gmk gmk
) {
140 gmk
.forEachObject((o
) {
142 if (o
.name
.length
== 0) assert(0, "can't register nameless object");
143 if (o
.name
in objByNameMap
) assert(0, "object '"~o
.name
~"' already registered");
144 auto tpl
= new ObjectTpl();
146 tpl
.idx
= (o
.parentobjidx
>= 0 ? o
.parentobjidx
: uint.max
); // temporarily abuse this field
147 tpl
.sprite_index
= o
.spridx
;
148 tpl
.mask_index
= o
.maskspridx
;
150 tpl
.visible
= o
.visible
;
152 tpl
.persistent
= o
.persistent
;
153 objByNameMap
[o
.name
] = tpl
;
159 foreach (immutable idx
, ObjectTpl tpl
; objects
[1..$]) {
160 if (tpl
.idx
!= uint.max
) {
161 auto po
= gmk
.objByNum(tpl
.idx
);
162 if (po
is null) assert(0, "invalid parent for object '"~tpl
.name
~"'");
163 if (auto px
= po
.name
in objByNameMap
) tpl
.parent
= *px
; else assert(0, "wtf?!");
165 tpl
.idx
= cast(uint)idx
; // fix index
171 uint objectByName (const(char)[] name
) {
172 if (auto tpp
= name
in objByNameMap
) return (*tpp
).idx
;
177 bool validObjectId (uint id
) { pragma(inline
, true); return (id
> 0 && id
< objects
.length
); }
180 // ////////////////////////////////////////////////////////////////////////// //
181 // circular double-linked list
186 void append (InstProxy o
) {
187 if (o
is null) return;
188 assert(o
.prev
is null);
189 assert(o
.next
is null);
190 // append to circular list
195 } else if (head
.next
is head
) {
196 // list has only one item
197 o
.prev
= o
.next
= head
;
198 head
.prev
= head
.next
= o
;
200 // list has more than one item
201 auto tail
= head
.prev
;
202 o
.prev
= tail
; // previous is list tail
203 o
.next
= head
; // next is list head
210 void remove (InstProxy o
) {
211 if (o
is null || o
.prev
is null) return;
212 assert(head
!is null);
213 // remove from circular list
214 if (head
.prev
is head
) {
219 // list has more than one item
220 if (head
is o
) head
= head
.next
; // deleting head item, move head
221 o
.prev
.next
= o
.next
;
222 o
.next
.prev
= o
.prev
;
224 o
.prev
= o
.next
= null;
230 // proxy for instance lists
231 private final class InstProxy
{
233 InstProxy prev
, next
;
236 this (Instance aself
, ObjectTpl aparent
=null) {
239 if (aself
!is null) {
240 if (aparent
!is null) aparent
.ilist
.append(this); else iall
.append(this);
244 void removeFromLists () {
246 if (parent
!is null) parent
.ilist
.remove(this); else iall
.remove(this);
252 // ////////////////////////////////////////////////////////////////////////// //
253 private __gshared InstList iall
; // all created instances
254 // single-linked list of all dead instances
255 private __gshared Instance deadList
;
259 // ////////////////////////////////////////////////////////////////////////// //
260 final class Instance
{
262 enum IdStart
= 100000;
263 __gshared
uint nextid
= IdStart
;
264 __gshared Instance
[uint] instById
;
265 __gshared
ulong curStep
= 0; // used for `with` management
268 Instance deadNext
; // in `deadList`
276 Real
[][] farrays
; // arrays for fields
277 ulong stepMark
; // used in `with` management
279 this (ObjectTpl aparent
) {
281 proxies
~= new InstProxy(this); // add to list of all instances
283 fields
.length
= fieldCount
;
285 // copy initial fields from parent object
286 if (aparent
!is null) {
287 fields
.ptr
[fi_id
] = Value(mId
);
288 fields
.ptr
[fi_sprite_index
] = Value(aparent
.sprite_index
);
289 fields
.ptr
[fi_mask_index
] = Value(aparent
.mask_index
);
290 fields
.ptr
[fi_solid
] = Value(aparent
.solid
);
291 fields
.ptr
[fi_visible
] = Value(aparent
.visible
);
292 fields
.ptr
[fi_depth
] = Value(aparent
.depth
);
293 fields
.ptr
[fi_persistent
] = Value(aparent
.persistent
);
295 // add to parents' lists
296 while (aparent
!is null) {
297 proxies
~= new InstProxy(this, aparent
);
298 aparent
= aparent
.parent
;
304 @property uint id () const pure nothrow @safe @nogc { return mId
; }
307 Real
get (uint fieldindex
) {
308 pragma(inline
, true);
309 return (fieldindex
< fields
.length ? fields
.ptr
[fieldindex
] : Value());
312 Real
get (uint fieldindex
, uint i0
, uint i1
=0) {
313 if (i0
>= 32000 || i1
>= 32000) return Value(); // out of range
314 if (fieldindex
>= farrays
.length
) return Value();
316 auto v
= farrays
.ptr
[fieldindex
];
317 return (i0
< v
.length ? v
.ptr
[i0
] : Value());
320 void set (Real val
, uint fieldindex
) {
321 if (fieldindex
< fields
.length
) fields
.ptr
[fieldindex
] = val
;
324 void set (Real val
, uint fieldindex
, uint i0
, uint i1
=0) {
325 if (i0
>= 32000 || i1
>= 32000) return; // out of range
326 if (fieldindex
>= fields
.length
) return;
328 if (farrays
.length
< fieldindex
+1) farrays
.length
= fieldindex
+1;
329 if (farrays
.ptr
[fieldindex
].length
< i0
+1) farrays
.ptr
[fieldindex
].length
= i0
+1;
330 farrays
.ptr
[fieldindex
].ptr
[i0
] = val
;
333 bool isInstanceOf (int objid
) {
334 if (objid
== ObjIdAll
) return true;
335 if (objid
<= 0 || objid
>= objects
.length || mParent
is null) return false;
336 for (ObjectTpl p
= objects
.ptr
[objid
]; p
!is null; p
= p
.parent
) if (mParent
is p
) return true;
341 if (deadNext
is null) {
345 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
); }
351 void advanceFrame () {
352 pragma(inline
, true);
356 // ////////////////////////////////////////////////////////////////////// //
359 private static struct Iterator
{
360 InstProxy head
; // starting instance
361 InstProxy cur
; // current instance
362 Instance si
; // instance for single-instance iterator
366 private __gshared Iterator
[] iters
;
367 private __gshared
uint itersUsed
= 1;
369 shared static this () {
370 iters
.length
= 1024; // arbitrary number
373 private uint newIId () {
374 pragma(inline
, true);
375 auto iid
= itersUsed
++;
376 if (iid
== iters
.length
) iters
.length
+= 1024;
380 // create new iterator, return iid or 0
381 uint newIterator (int objid
, uint aOldSelf
) {
382 if (itersUsed
>= short.max
) assert(0, "too many iterators");
384 if (objid
>= IdStart
) {
385 if (auto i
= cast(uint)objid
in instById
) {
387 iters
.ptr
[iid
].oldSelf
= aOldSelf
;
388 iters
.ptr
[iid
].si
= *i
;
394 if (objid
== ObjIdAll
) {
395 if (iall
.head
is null) return 0; // no instances yet
397 with (iters
.ptr
[iid
]) { step
= curStep
++; head
= cur
= iall
.head
; oldSelf
= aOldSelf
; }
401 if (objid
<= 0) return 0;
402 if (objid
< objects
.length
) {
404 if (auto proxy
= objects
.ptr
[objid
].ilist
.head
) {
406 with (iters
.ptr
[iid
]) { step
= curStep
++; head
= cur
= proxy
; oldSelf
= aOldSelf
; }
414 // returns current object or 0 on completion, move iterator to next object
415 uint iteratorNext (uint iid
) {
416 if (iid
== 0 || iid
>= itersUsed
) return 0;
417 auto it
= &iters
.ptr
[iid
];
418 if (it
.head
is null) {
419 if (it
.si
is null) return 0; // dead iterator
420 auto res
= (!it
.si
.mDead ? it
.si
.mId
: 0);
426 auto ri
= it
.cur
.self
;
427 if ((it
.cur
= it
.cur
.next
) is it
.head
) it
.head
= it
.cur
= null;
428 if (!ri
.mDead
&& ri
.stepMark
<= it
.step
) return ri
.mId
; // good instance
429 // bad instance (either dead, or newborn) move on
430 } while (it
.cur
!is null);
431 return 0; // no more instances
434 uint iteratorOldSelf (uint iid
) {
435 pragma(inline
, true);
436 return (iid
== 0 || iid
>= itersUsed ?
0 : iters
.ptr
[iid
].oldSelf
);
439 void freeAllIterators () {
441 foreach (ref it
; iters
[1..itersUsed
]) { it
.head
= it
.cur
= null; it
.si
= null; }
447 // return `true` from delegate to stop
448 // will skip dead instances
449 Instance
forEach (int objid
, scope bool delegate (Instance inst
) dg
) {
452 if (objid
>= IdStart
) {
453 if (auto i
= cast(uint)objid
in instById
) return (dg(*i
) ?
*i
: null);
458 if (objid
== ObjIdAll
) {
459 head
= cur
= iall
.head
;
460 } else if (objid
> 0 && objid
< objects
.length
) {
461 if ((head
= cur
= objects
.ptr
[objid
].ilist
.head
) is null) return null;
467 if (!cur
.self
.mDead
&& dg(cur
.self
)) return cur
.self
;
468 if ((cur
= cur
.next
) is head
) break;
473 Instance
firstInstanceOf (int id
) {
475 if (auto pid
= cast(uint)id
in instById
) return *pid
;
476 } else if (id
> 0 && id
< objects
.length
) {
477 if (auto px
= objects
.ptr
[id
].ilist
.head
) return px
.self
;
483 void freeDeadObjects () {
484 while (deadList
!is null) {
485 assert(deadList
.mDead
);
486 foreach (InstProxy px
; deadList
.proxies
) px
.removeFromLists();
487 // remove from alive instances hash
488 // do it here instead of `kill()`, so stored objids will still work until script end
489 instById
.remove(deadList
.mId
);
490 deadList
= deadList
.deadNext
;
495 // this should be called when top-level script execution is complete
496 void scriptComplete () {