vm and compiler fixes; some work on instance system
[gaemu.git] / gaem / runner / objects.d
blob1c1e922e2aa196629c7a3173e84cff6f2ddceda6
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_parent",
44 "sprite_index",
45 "mask_index",
46 "solid",
47 "visible",
48 "depth",
49 "persistent",
50 "id",
51 "x",
52 "y",
53 "direction",
54 "image_index",
55 "image_xscale",
56 "image_yscale",
57 "image_angle",
58 "image_blend",
59 "image_alpha",
60 "sprite_width",
61 "sprite_height",
65 // create `fi_xxx` variables
66 mixin({
67 string res;
68 foreach (string name; PredefinedFields) res ~= "private __gshared uint fi_"~name~";\n";
69 return res;
70 }());
72 // create predefined fields
73 shared static this () {
74 mixin({
75 string res;
76 foreach (string name; PredefinedFields) res ~= "fi_"~name~" = allocateFieldId(`"~name~"`);\n";
77 return res;
78 }());
82 package(gaem.runner) uint fieldCount () { pragma(inline, true); return cast(uint)fields.length; }
85 // ////////////////////////////////////////////////////////////////////////// //
86 // game object (instance template)
87 class ObjectTpl {
88 string name;
89 ObjectTpl parent; // 0: no parent -- root object
90 uint idx; // globally unique index (should never be zero)
91 uint sprite_index;
92 uint mask_index;
93 bool solid;
94 bool visible;
95 int depth;
96 bool persistent;
98 uint[][GMEvent.Type.max+1] events; //TODO
100 InstList ilist; // all instances with this object in parent chain
103 private __gshared ObjectTpl[] objects; // 0 is unused
104 shared static this () { objects.length = 1; }
106 enum ObjIdAll = -666;
109 // ////////////////////////////////////////////////////////////////////////// //
110 // circular double-linked list
111 struct InstList {
112 InstProxy head;
113 uint count;
115 void append (InstProxy o) {
116 if (o is null) return;
117 assert(o.prev is null);
118 assert(o.next is null);
119 // append to circular list
120 if (head is null) {
121 // list has no items
122 head = o;
123 o.prev = o.next = o;
124 } else if (head.next is head) {
125 // list has only one item
126 o.prev = o.next = head;
127 head.prev = head.next = o;
128 } else {
129 // list has more than one item
130 auto tail = head.prev;
131 o.prev = tail; // previous is list tail
132 o.next = head; // next is list head
133 tail.next = o;
134 head.prev = o;
136 ++count;
139 void remove (InstProxy o) {
140 if (o is null || o.prev is null) return;
141 assert(head !is null);
142 // remove from circular list
143 if (head.prev is head) {
144 // list has one item
145 assert(head is o);
146 head = null;
147 } else {
148 // list has more than one item
149 if (head is o) head = head.next; // deleting head item, move head
150 o.prev.next = o.next;
151 o.next.prev = o.prev;
153 o.prev = o.next = null;
154 --count;
159 // proxy for instance lists
160 private final class InstProxy {
161 Instance self;
162 InstProxy prev, next;
163 ObjectTpl parent;
165 this (Instance aself, ObjectTpl aparent=null) {
166 self = aself;
167 parent = aparent;
168 if (aself !is null) {
169 if (aparent !is null) aparent.ilist.append(this); else iall.append(this);
173 void removeFromLists () {
174 if (next !is null) {
175 if (parent !is null) parent.ilist.remove(this); else iall.remove(this);
181 // ////////////////////////////////////////////////////////////////////////// //
182 private __gshared InstList iall; // all created instances
183 // single-linked list of all dead instances
184 private __gshared Instance deadList;
188 // ////////////////////////////////////////////////////////////////////////// //
189 final class Instance {
190 private:
191 enum IdStart = 100000;
192 __gshared uint nextid = IdStart;
193 __gshared Instance[uint] instById;
194 __gshared uint curStep = 0;
196 private:
197 Instance deadNext; // in `deadList`
199 private:
200 uint mId;
201 ObjectTpl mParent;
202 bool mDead;
203 InstProxy[] proxies;
204 Real[] fields;
205 uint stepMark; // if this != curStep, the object is just created and should be skipped
207 this (ObjectTpl aparent) {
208 mId = nextid++;
209 proxies ~= new InstProxy(this); // add to list of all instances
210 mParent = aparent;
211 fields.length = fieldCount;
212 fields[] = Value();
213 // copy initial fields from parent object
214 if (aparent !is null) {
215 fields.ptr[fi_id] = Value(mId);
216 fields.ptr[fi_sprite_index] = Value(aparent.sprite_index);
217 fields.ptr[fi_mask_index] = Value(aparent.mask_index);
218 fields.ptr[fi_solid] = Value(aparent.solid);
219 fields.ptr[fi_visible] = Value(aparent.visible);
220 fields.ptr[fi_depth] = Value(aparent.depth);
221 fields.ptr[fi_persistent] = Value(aparent.persistent);
223 // add to parents' lists
224 while (aparent !is null) {
225 proxies ~= new InstProxy(this, aparent);
226 aparent = aparent.parent;
228 stepMark = curStep+1;
231 public:
232 bool isInstanceOf (int objid) {
233 if (objid == ObjIdAll) return true;
234 if (objid <= 0 || objid >= objects.length || mParent is null) return false;
235 for (ObjectTpl p = objects.ptr[objid]; p !is null; p = p.parent) if (mParent is p) return true;
236 return false;
239 void kill () {
240 if (deadNext is null) {
241 mDead = true;
242 deadNext = deadList;
243 deadList = this;
244 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); }
248 static:
249 // should be called
250 void advanceFrame () {
251 pragma(inline, true);
252 ++curStep;
255 // ////////////////////////////////////////////////////////////////////// //
256 // iterators API
257 static:
258 private static struct Iterator {
259 InstProxy head; // starting instance
260 InstProxy cur; // current instance
261 Instance si; // instance for single-instance iterator
263 private __gshared Iterator[] iters;
264 private __gshared uint itersUsed = 1;
266 shared static this () {
267 iters.length = 1024; // arbitrary number
270 private uint newIId () {
271 pragma(inline, true);
272 auto iid = itersUsed++;
273 if (iid == iters.length) iters.length += 1024;
274 return iid;
277 // create new iterator, return iid
278 uint newIterator (int objid) {
279 if (itersUsed >= short.max) assert(0, "too many iterators");
280 // instance id?
281 if (objid >= IdStart) {
282 if (auto i = cast(uint)objid in instById) {
283 auto iid = newIId;
284 iters.ptr[iid].si = *i;
285 return iid;
287 return 0;
289 // "all" object?
290 if (objid == ObjIdAll) {
291 if (iall.head is null) return 0; // no instances yet
292 auto iid = newIId;
293 with (iters.ptr[iid]) head = cur = iall.head;
294 return iid;
296 // "none" object?
297 if (objid <= 0) return 0;
298 if (objid < objects.length) {
299 // object class
300 if (auto proxy = objects.ptr[objid].ilist.head) {
301 auto iid = newIId;
302 with (iters.ptr[iid]) head = cur = proxy;
303 return iid;
306 // alas
307 return 0;
310 // returns current object or 0 on completion, move iterator to next object
311 uint iteratorNext (uint iid) {
312 if (iid == 0 || iid >= itersUsed) return 0;
313 auto it = &iters.ptr[iid];
314 if (it.head is null) {
315 if (it.si is null) return 0; // dead iterator
316 auto res = (!it.si.mDead ? it.si.mId : 0);
317 it.si = null;
318 return res;
320 // normal iterator
321 do {
322 auto ri = it.cur.self;
323 if ((it.cur = it.cur.next) is it.head) it.head = it.cur = null;
324 if (!ri.mDead) return ri.mId; // good instance
325 // bad instance, move on
326 } while (it.cur !is null);
327 return 0; // no more instances
330 void freeAllIterators () {
331 if (itersUsed > 1) {
332 foreach (ref it; iters[1..itersUsed]) { it.head = it.cur = null; it.si = null; }
333 itersUsed = 1;
337 static:
338 // return `true` from delegate to stop
339 // will skip dead instances
340 Instance forEach (int objid, scope bool delegate (Instance inst) dg) {
341 assert(dg !is null);
342 // instance?
343 if (objid >= IdStart) {
344 if (auto i = cast(uint)objid in instById) return (dg(*i) ? *i : null);
345 return null;
347 // object?
348 InstProxy head, cur;
349 if (objid == ObjIdAll) {
350 head = cur = iall.head;
351 } else if (objid > 0 && objid < objects.length) {
352 if ((head = cur = objects.ptr[objid].ilist.head) is null) return null;
353 } else {
354 return null;
356 // go on
357 for (;;) {
358 if (!cur.self.mDead && dg(cur.self)) return cur.self;
359 if ((cur = cur.next) is head) break;
361 return null;
364 static:
365 void freeDeadObjects () {
366 while (deadList !is null) {
367 assert(deadList.mDead);
368 foreach (InstProxy px; deadList.proxies) px.removeFromLists();
369 deadList = deadList.deadNext;
375 public: