switched to GPLv3 ONLY, because i don't trust FSF anymore
[gaemu.git] / gaem / runner / objects.d
blob58a7d903c8c2381454cdb59349473e56456d3c77
1 /* GML runner
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;
19 import gaem.ungmk;
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");
36 fields[name] = fid;
37 return cast(short)fid;
41 private enum PredefinedFields = [
42 "object_index", // int
43 "id", // 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
55 "image_blend", // int
56 "mask_index", // int
57 "depth", // Real
58 "x", // Real
59 "y", // Real
60 "xstart", // Real
61 "ystart", // Real
62 "xprevious", // Real
63 "yprevious", // Real
64 "direction", // Real
65 "speed", // Real
66 "hspeed", // Real
67 "vspeed", // Real
68 "friction", // Real
69 "gravity_direction", // Real
70 "gravity", // Real
71 "bbox_left", // int
72 "bbox_right", // int
73 "bbox_top", // int
74 "bbox_bottom", // int
75 "visible", // int
76 "solid", // int
77 "persistent", // int
78 "alarm0", // int
79 "alarm1", // int
80 "alarm2", // int
81 "alarm3", // int
82 "alarm4", // int
83 "alarm5", // int
84 "alarm6", // int
85 "alarm7", // int
86 "alarm8", // int
87 "alarm9", // int
88 "alarm10", // int
89 "alarm11", // int
93 // create `fi_xxx` variables
94 mixin({
95 string res;
96 foreach (string name; PredefinedFields) res ~= "private __gshared uint fi_"~name~";\n";
97 return res;
98 }());
100 // create predefined fields
101 shared static this () {
102 mixin({
103 string res;
104 foreach (string name; PredefinedFields) res ~= "fi_"~name~" = allocateFieldId(`"~name~"`);\n";
105 return res;
106 }());
110 package(gaem.runner) uint fieldCount () { pragma(inline, true); return cast(uint)fields.length; }
113 // ////////////////////////////////////////////////////////////////////////// //
114 // game object (instance template)
115 final class ObjectTpl {
116 string name;
117 ObjectTpl parent; // 0: no parent -- root object
118 uint idx; // globally unique index (should never be zero)
119 uint sprite_index;
120 uint mask_index;
121 bool solid;
122 bool visible;
123 int depth;
124 bool persistent;
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) {
141 assert(o !is null);
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();
145 tpl.name = o.name;
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;
149 tpl.solid = o.solid;
150 tpl.visible = o.visible;
151 tpl.depth = o.depth;
152 tpl.persistent = o.persistent;
153 objByNameMap[o.name] = tpl;
154 objects ~= tpl;
155 return false;
158 // now fix parents
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
170 // 0: no such object
171 uint objectByName (const(char)[] name) {
172 if (auto tpp = name in objByNameMap) return (*tpp).idx;
173 return 0;
177 bool validObjectId (uint id) { pragma(inline, true); return (id > 0 && id < objects.length); }
180 // ////////////////////////////////////////////////////////////////////////// //
181 // circular double-linked list
182 struct InstList {
183 InstProxy head;
184 uint count;
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
191 if (head is null) {
192 // list has no items
193 head = o;
194 o.prev = o.next = o;
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;
199 } else {
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
204 tail.next = o;
205 head.prev = o;
207 ++count;
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) {
215 // list has one item
216 assert(head is o);
217 head = null;
218 } else {
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;
225 --count;
230 // proxy for instance lists
231 private final class InstProxy {
232 Instance self;
233 InstProxy prev, next;
234 ObjectTpl parent;
236 this (Instance aself, ObjectTpl aparent=null) {
237 self = aself;
238 parent = aparent;
239 if (aself !is null) {
240 if (aparent !is null) aparent.ilist.append(this); else iall.append(this);
244 void removeFromLists () {
245 if (next !is null) {
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 {
261 private:
262 enum IdStart = 100000;
263 __gshared uint nextid = IdStart;
264 __gshared Instance[uint] instById;
265 __gshared ulong curStep = 0; // used for `with` management
267 private:
268 Instance deadNext; // in `deadList`
270 private:
271 uint mId;
272 ObjectTpl mParent;
273 bool mDead;
274 InstProxy[] proxies;
275 Real[] fields;
276 Real[][] farrays; // arrays for fields
277 ulong stepMark; // used in `with` management
279 this (ObjectTpl aparent) {
280 mId = nextid++;
281 proxies ~= new InstProxy(this); // add to list of all instances
282 mParent = aparent;
283 fields.length = fieldCount;
284 fields[] = Value();
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;
300 stepMark = curStep;
303 public:
304 @property uint id () const pure nothrow @safe @nogc { return mId; }
306 public:
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();
315 i0 |= i1<<16;
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;
327 i0 |= i1<<16;
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;
337 return false;
340 void kill () {
341 if (deadNext is null) {
342 mDead = true;
343 deadNext = deadList;
344 deadList = this;
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); }
349 static:
350 // should be called
351 void advanceFrame () {
352 pragma(inline, true);
353 ++curStep;
356 // ////////////////////////////////////////////////////////////////////// //
357 // iterators API
358 static:
359 private static struct Iterator {
360 InstProxy head; // starting instance
361 InstProxy cur; // current instance
362 Instance si; // instance for single-instance iterator
363 ulong step;
364 uint oldSelf;
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;
377 return iid;
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");
383 // instance id?
384 if (objid >= IdStart) {
385 if (auto i = cast(uint)objid in instById) {
386 auto iid = newIId;
387 iters.ptr[iid].oldSelf = aOldSelf;
388 iters.ptr[iid].si = *i;
389 return iid;
391 return 0;
393 // "all" object?
394 if (objid == ObjIdAll) {
395 if (iall.head is null) return 0; // no instances yet
396 auto iid = newIId;
397 with (iters.ptr[iid]) { step = curStep++; head = cur = iall.head; oldSelf = aOldSelf; }
398 return iid;
400 // "none" object?
401 if (objid <= 0) return 0;
402 if (objid < objects.length) {
403 // object class
404 if (auto proxy = objects.ptr[objid].ilist.head) {
405 auto iid = newIId;
406 with (iters.ptr[iid]) { step = curStep++; head = cur = proxy; oldSelf = aOldSelf; }
407 return iid;
410 // alas
411 return 0;
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);
421 it.si = null;
422 return res;
424 // normal iterator
425 do {
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 () {
440 if (itersUsed > 1) {
441 foreach (ref it; iters[1..itersUsed]) { it.head = it.cur = null; it.si = null; }
442 itersUsed = 1;
446 static:
447 // return `true` from delegate to stop
448 // will skip dead instances
449 Instance forEach (int objid, scope bool delegate (Instance inst) dg) {
450 assert(dg !is null);
451 // instance?
452 if (objid >= IdStart) {
453 if (auto i = cast(uint)objid in instById) return (dg(*i) ? *i : null);
454 return null;
456 // object?
457 InstProxy head, cur;
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;
462 } else {
463 return null;
465 // go on
466 for (;;) {
467 if (!cur.self.mDead && dg(cur.self)) return cur.self;
468 if ((cur = cur.next) is head) break;
470 return null;
473 Instance firstInstanceOf (int id) {
474 if (id >= IdStart) {
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;
479 return null;
482 static:
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;
494 static:
495 // this should be called when top-level script execution is complete
496 void scriptComplete () {
497 freeAllIterators();
498 freeDeadObjects();
503 public: