added missing opcodes to VM
[gaemu.git] / gaem / runner / objects.d
blobc6706456a9c55cbec5d5abcd309f00318b900a66
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, 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;
20 import gaem.ungmk;
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");
37 fields[name] = fid;
38 return cast(short)fid;
42 private enum PredefinedFields = [
43 "object_index", // int
44 "id", // 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
56 "image_blend", // int
57 "mask_index", // int
58 "depth", // Real
59 "x", // Real
60 "y", // Real
61 "xstart", // Real
62 "ystart", // Real
63 "xprevious", // Real
64 "yprevious", // Real
65 "direction", // Real
66 "speed", // Real
67 "hspeed", // Real
68 "vspeed", // Real
69 "friction", // Real
70 "gravity_direction", // Real
71 "gravity", // Real
72 "bbox_left", // int
73 "bbox_right", // int
74 "bbox_top", // int
75 "bbox_bottom", // int
76 "visible", // int
77 "solid", // int
78 "persistent", // int
79 "alarm0", // int
80 "alarm1", // int
81 "alarm2", // int
82 "alarm3", // int
83 "alarm4", // int
84 "alarm5", // int
85 "alarm6", // int
86 "alarm7", // int
87 "alarm8", // int
88 "alarm9", // int
89 "alarm10", // int
90 "alarm11", // int
94 // create `fi_xxx` variables
95 mixin({
96 string res;
97 foreach (string name; PredefinedFields) res ~= "private __gshared uint fi_"~name~";\n";
98 return res;
99 }());
101 // create predefined fields
102 shared static this () {
103 mixin({
104 string res;
105 foreach (string name; PredefinedFields) res ~= "fi_"~name~" = allocateFieldId(`"~name~"`);\n";
106 return res;
107 }());
111 package(gaem.runner) uint fieldCount () { pragma(inline, true); return cast(uint)fields.length; }
114 // ////////////////////////////////////////////////////////////////////////// //
115 // game object (instance template)
116 final class ObjectTpl {
117 string name;
118 ObjectTpl parent; // 0: no parent -- root object
119 uint idx; // globally unique index (should never be zero)
120 uint sprite_index;
121 uint mask_index;
122 bool solid;
123 bool visible;
124 int depth;
125 bool persistent;
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) {
142 assert(o !is null);
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();
146 tpl.name = o.name;
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;
150 tpl.solid = o.solid;
151 tpl.visible = o.visible;
152 tpl.depth = o.depth;
153 tpl.persistent = o.persistent;
154 objByNameMap[o.name] = tpl;
155 objects ~= tpl;
156 return false;
159 // now fix parents
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
171 // 0: no such object
172 uint objectByName (const(char)[] name) {
173 if (auto tpp = name in objByNameMap) return (*tpp).idx;
174 return 0;
178 bool validObjectId (uint id) { pragma(inline, true); return (id > 0 && id < objects.length); }
181 // ////////////////////////////////////////////////////////////////////////// //
182 // circular double-linked list
183 struct InstList {
184 InstProxy head;
185 uint count;
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
192 if (head is null) {
193 // list has no items
194 head = o;
195 o.prev = o.next = o;
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;
200 } else {
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
205 tail.next = o;
206 head.prev = o;
208 ++count;
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) {
216 // list has one item
217 assert(head is o);
218 head = null;
219 } else {
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;
226 --count;
231 // proxy for instance lists
232 private final class InstProxy {
233 Instance self;
234 InstProxy prev, next;
235 ObjectTpl parent;
237 this (Instance aself, ObjectTpl aparent=null) {
238 self = aself;
239 parent = aparent;
240 if (aself !is null) {
241 if (aparent !is null) aparent.ilist.append(this); else iall.append(this);
245 void removeFromLists () {
246 if (next !is null) {
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 {
262 private:
263 enum IdStart = 100000;
264 __gshared uint nextid = IdStart;
265 __gshared Instance[uint] instById;
266 __gshared ulong curStep = 0; // used for `with` management
268 private:
269 Instance deadNext; // in `deadList`
271 private:
272 uint mId;
273 ObjectTpl mParent;
274 bool mDead;
275 InstProxy[] proxies;
276 Real[] fields;
277 Real[][] farrays; // arrays for fields
278 ulong stepMark; // used in `with` management
280 this (ObjectTpl aparent) {
281 mId = nextid++;
282 proxies ~= new InstProxy(this); // add to list of all instances
283 mParent = aparent;
284 fields.length = fieldCount;
285 fields[] = Value();
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;
301 stepMark = curStep;
304 public:
305 @property uint id () const pure nothrow @safe @nogc { return mId; }
307 public:
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();
316 i0 |= i1<<16;
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;
328 i0 |= i1<<16;
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;
338 return false;
341 void kill () {
342 if (deadNext is null) {
343 mDead = true;
344 deadNext = deadList;
345 deadList = this;
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); }
350 static:
351 // should be called
352 void advanceFrame () {
353 pragma(inline, true);
354 ++curStep;
357 // ////////////////////////////////////////////////////////////////////// //
358 // iterators API
359 static:
360 private static struct Iterator {
361 InstProxy head; // starting instance
362 InstProxy cur; // current instance
363 Instance si; // instance for single-instance iterator
364 ulong step;
365 uint oldSelf;
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;
378 return iid;
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");
384 // instance id?
385 if (objid >= IdStart) {
386 if (auto i = cast(uint)objid in instById) {
387 auto iid = newIId;
388 iters.ptr[iid].oldSelf = aOldSelf;
389 iters.ptr[iid].si = *i;
390 return iid;
392 return 0;
394 // "all" object?
395 if (objid == ObjIdAll) {
396 if (iall.head is null) return 0; // no instances yet
397 auto iid = newIId;
398 with (iters.ptr[iid]) { step = curStep++; head = cur = iall.head; oldSelf = aOldSelf; }
399 return iid;
401 // "none" object?
402 if (objid <= 0) return 0;
403 if (objid < objects.length) {
404 // object class
405 if (auto proxy = objects.ptr[objid].ilist.head) {
406 auto iid = newIId;
407 with (iters.ptr[iid]) { step = curStep++; head = cur = proxy; oldSelf = aOldSelf; }
408 return iid;
411 // alas
412 return 0;
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);
422 it.si = null;
423 return res;
425 // normal iterator
426 do {
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 () {
441 if (itersUsed > 1) {
442 foreach (ref it; iters[1..itersUsed]) { it.head = it.cur = null; it.si = null; }
443 itersUsed = 1;
447 static:
448 // return `true` from delegate to stop
449 // will skip dead instances
450 Instance forEach (int objid, scope bool delegate (Instance inst) dg) {
451 assert(dg !is null);
452 // instance?
453 if (objid >= IdStart) {
454 if (auto i = cast(uint)objid in instById) return (dg(*i) ? *i : null);
455 return null;
457 // object?
458 InstProxy head, cur;
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;
463 } else {
464 return null;
466 // go on
467 for (;;) {
468 if (!cur.self.mDead && dg(cur.self)) return cur.self;
469 if ((cur = cur.next) is head) break;
471 return null;
474 Instance firstInstanceOf (int id) {
475 if (id >= IdStart) {
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;
480 return null;
483 static:
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;
495 static:
496 // this should be called when top-level script execution is complete
497 void scriptComplete () {
498 freeAllIterators();
499 freeDeadObjects();
504 public: