-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathEventHandlr.js
373 lines (313 loc) · 12 KB
/
EventHandlr.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
/* EventHandlr.js
* A timed events library derived from Full Screen Mario
* This has two functions:
* 1. Provide a flexible alternative to setTimeout and setInterval that
* respects pauses and resumes in time (such as from game pauses)
* 2. Provide functions to automatically 'cycle' between certain classes
* on an object
*/
function EventHandlr(settings) {
"use strict";
/* Member Variables
*/
var version = "1.0",
// The current (most recently reached) game time
time,
// An int->event hash table of events to be run
events,
// Default attribute names, so they can be overriden
cycles,
className,
onSpriteCycleStart,
doSpriteCycleStart,
cycleCheckValidity,
// Default time separations
timingDefault,
// Function handlers
addClass,
removeClass;
/* Simple gets
*/
this.getTime = function() { return time; };
this.getEvents = function() { return events; };
/* Event Adding (simple)
* Sample usage:
* addEvent(
* function(name, of, arguments) { ... },
* time_until_execution,
* arg1, arg2, arg3
* );
*/
// Public: addEvent
// Equivalent to setTimeout
// Adds a function to execute at a particular time, with arguments
var addEvent = this.addEvent = function(func, time_exec) {
// Make sure func is actually a function
if(!(func instanceof Function)) {
console.warn("Attempting to add an event that isn't a function.");
console.log(arguments);
return false;
}
time_exec = time_exec || 1;
// Grab arguments to be passed to the function, excluding func and time_exec
var args = arrayMake(arguments);
args.splice(0,2);
// Create the event, keeping track of start and end times
var event = {
func: func,
time_exec: time + time_exec,
time_repeat: time_exec,
args: args,
repeat: 1
};
// Add the event to events, then return it
insertEvent(event, event.time_exec);
return event;
};
// Public: addEventInterval
// Equivalent to setInterval
// Similar to addEvent, but it will be repeated a specified number of times
// Time delay until execution is the same as the time between subsequent executions
var addEventInterval = this.addEventInterval = function(func, time_exec, num_repeats) {
// Make sure func is actually a function
if(!(func instanceof Function)) {
console.warn("Attempting to add an event that isn't a function.");
console.log(arguments);
return false;
}
time_exec = time_exec || 1;
num_repeats = num_repeats || 1;
// Grab arguments to be passed, excluding func, time_exec, and num_repeats
var args = arrayMake(arguments);
args.splice(0, 3);
// Create the event, keeping track of start and end times, and repetitions
var event = {
func: func,
time_exec: time + time_exec,
time_repeat: time_exec,
args: args,
repeat: num_repeats
};
// These may need to have a reference to the event from the function
func.event = event;
// Add the event to events, then return it
insertEvent(event, event.time_exec);
return event;
};
// Public: addEventIntervalSynched
// Delays the typical addEventInterval until it's synched with time
// (this goes by basic modular arithmetic)
var addEventIntervalSynched = this.addEventIntervalSynched = function(func, time_exec, num_repeats, me, settings) {
var calctime = time_exec * settings.length,
entry = ceil(time / calctime) * calctime,
scope = this,
addfunc = function(scope, args, me) {
me.startcount = time;
return addEventInterval.apply(scope, args);
};
time_exec = time_exec || 1;
num_repeats = num_repeats || 1;
// If there's no difference in times, you're good to go
if(entry == time) {
return addfunc(scope, arguments, me);
}
// Otherwise it should be delayed until the time is right
else {
var dt = entry - time;
addEvent(addfunc, dt, scope, arguments, me);
}
};
// Quick handler to add an event at a particular time
// An array must exist so multiple events can be at the same time
function insertEvent(event, time) {
// If it doesn't yet have an array, event will be the only contents
if(!events[time]) return events[time] = [event];
// Otherwise push it to there
events[time].push(event);
return events[time];
}
/* Event Removing (simple)
*/
// Public: clearEvent
// Makes an event not happen again
var clearEvent = this.clearEvent = function(event) {
if(!event) return;
// Telling it not to repeat anymore is enough
event.repeat = 0;
};
// Public: clearAllEvents
// Completely cancels all events
var clearAllEvents = this.clearAllEvents = function() {
events = {};
};
// Given an object, clear its class cycle under a given name
var clearClassCycle = this.clearClassCycle = function(me, name) {
if(!me[cycles] || !me[cycles][name]) return;
var cycle = me[cycles][name];
cycle[0] = false;
cycle.length = false;
delete me[cycles][name];
};
// Given an object, clear all its class cycles
var clearAllCycles = this.clearAllCycles = function(me) {
var cycles = me[cycles],
name, cycle;
for(name in cycles) {
cycle = cycles[name];
cycle[0] = false;
cycle.length = 1;
delete cycles[name];
}
};
/* Sprite Cycles (advanced)
* Functions to cycle a given objects [className] attribute through an array of names
* Sample usage:
* addSpriteCycle(
* me,
* ["run_one", "run_two", "run_three"]
* "running",
* 7
* );
* Note: These require handlers from the user, such as those given by FullScreenMario
*/
// Public: addSpriteCycle
// Sets a sprite cycle (settings) for an object under name
var addSpriteCycle = this.addSpriteCycle = function(me, settings, name, timing) {
// Make sure the object has a holder for cycles...
if(!me[cycles]) me[cycles] = {};
// ...and nothing previously existing for that name
clearClassCycle(me, name);
var timingIsFunc = typeof(timing) == "function";
name = name || 0;
// Set the cycle under me[cycles][name]
var cycle = me[cycles][name] = setSpriteCycle(me, settings, timingIsFunc ? 0 : timing);
// If there is a timing function, make it the count changer
if(cycle.event && timingIsFunc)
cycle.event.count_changer = timing;
// Immediately run the first class cycle, then return
cycleClass(me, settings);
return cycle;
};
// Public: addSpriteCycleSynched
// Delays the typical addSpriteCycle until it's synched with time
// (Note: very similar to addSpriteCycle, and could probably be combined)
var addSpriteCycleSynched = this.addSpriteCycleSynched = function(me, settings, name, timing) {
// Make sure the object has a holder for cycles...
if(!me[cycles]) me[cycles] = {};
// ...and nothing previously existing for that name
clearClassCycle(me, name);
// Set the cycle under me[cycles][name]
name = name || 0;
var cycle = me[cycles][name] = setSpriteCycle(me, settings, timing, true);
// Immediately run the first class cycle, then return
cycleClass(me, settings);
return cycle;
};
// Initializes a sprite cycle for an object
function setSpriteCycle(me, settings, timing, synched) {
// Start off before the beginning of the cycle
settings.loc = settings.oldclass = -1;
// Let the object know to start the cycle when needed
var func = synched ? addEventIntervalSynched : addEventInterval;
me[onSpriteCycleStart] = function() { func(cycleClass, timing || timingDefault, Infinity, me, settings); };
// If it should already start, do that
if(me[doSpriteCycleStart])
me[onSpriteCycleStart]();
return settings;
}
// Moves an object from its current class in the sprite cycle to the next one
function cycleClass(me, settings) {
// If anything has been invalidated, return true to stop
if(!me || !settings || !settings.length) return true;
if(cycleCheckValidity != null && !me[cycleCheckValidity]) return true;
// Get rid of the previous class, from settings (-1 by default)
if(settings.oldclass != -1 && settings.oldclass !== "")
removeClass(me, settings.oldclass);
// Move to the next location in settings, as a circular list
settings.loc = ++settings.loc % settings.length;
// Current is the sprite, bool, or function currently being added and/or run
var current = settings[settings.loc];
// If it isn't false or non-existant, (run if needed and) get it as the next name
if(current) {
var name = current instanceof Function ? current(me, settings) : current;
// If the next name is a string, set that as the old class, and add it
if(typeof(name) == "string") {
settings.oldclass = name;
addClass(me, name);
return false;
}
// For non-strings, return true (to stop) if the name evaluated to be false
else return (name === false);
}
// Otherwise since current was false, return true (to stop) if it's === false
else return (current === false);
}
/* Event Handling
*/
// Public: handleEvents
// Increments time and runs all events at the new events[time]
this.handleEvents = function() {
++time;
var events_current = events[time];
if(!events_current) return; // If there isn't anything to run, don't even bother
var event, len, i;
// For each event currently scheduled:
for(i = 0, len = events_current.length; i < len; ++i) {
event = events_current[i];
// Call the function, using apply to pass in arguments dynamically
// If running it returns true, it's done; otherwise check if it should go again
if(event.repeat > 0 && !event.func.apply(this, event.args)) {
// If it has a count changer (and needs to modify itself), do that
if(event.count_changer) event.count_changer(event);
// If repeat is a function, running it determines whether to repeat
if(event.repeat instanceof Function) {
// Binding then calling is what actually runs the function
if((event.repeat.bind(event))()) {
event.count += event.time_repeat;
insertEvent(event, event.time_exec);
}
}
// Otherwise it's a number: decrement it, and if it's > 0, repeat.
else if(--event.repeat > 0) {
event.time_exec += event.time_repeat;
insertEvent(event, event.time_exec);
}
}
}
// Once all these events are done, ignore the memory
delete events[time];
};
/* Utility functions
*/
// Looking at you, function arguments
function arrayMake(args) {
return Array.prototype.slice.call(args);
}
// Simple expressions to add/remove classes
function classAdd(me, strin) {
me.className += " " + strin;
}
function classRemove(me, strout) {
me.className = me.className.replace(new RegExp(" " + strout, "gm"), "");
}
// Quick reference for math
var ceil = Math.ceil;
/* Reset
*/
function reset(settings) {
time = settings.time || 0;
events = settings.events || {};
// Attribute names
cycles = settings.cycles || "cycles";
className = settings.className || "className";
onSpriteCycleStart = settings.onSpriteCycleStart || "onSpriteCycleStart";
doSpriteCycleStart = settings.doSpriteCycleStart || "doSpriteCycleStart";
cycleCheckValidity = settings.cycleCheckValidity;
// Timing numbers
timingDefault = settings.timingDefault || 7;
// Function handlers
addClass = settings.addClass || window.addClass || classAdd;
removeClass = settings.removeClass || window.removeClass || classRemove;
}
reset(settings || {});
}