Skip to content

Commit

Permalink
Merge pull request #5 from JustinPinner/1.0.3
Browse files Browse the repository at this point in the history
1.0.3
  • Loading branch information
JustinPinner authored Oct 6, 2019
2 parents b085185 + f4fd945 commit a73fd01
Show file tree
Hide file tree
Showing 10 changed files with 425 additions and 327 deletions.
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,17 @@ So, a small, capable power plant for use in a wide range of applications. And al
TODO!

But in the meantime, and in the absence of any useful docs, here's my work-in-progress [astrowars](https://github.com/JustinPinner/astrowars) example of how the engine can be (ab)used.

## Versions

1.0.3
=====
* Updated dependencies
* Added a `Formatter` to the `Engine` to ease string padding / justification and lightweight date/time strings for logging
* Added a `deleteObjectById` method to`Engine` to make it easier to kill off a specific `gameObject` for whatever reason
* Export the Formatter class so you can use it in places other than via the `Engine`
* Added debugging to see what's going on inside events, timers and other engine functionality. Activate it by adding a `?debug` search param to your game url in your browser
* Added a `Logger` class that sends debug info (see above) to the browser console
* Changed the way that objects deregister their `xxx-Loaded` event - added a timed call to its `initDone` method. You now have 500ms to complete your custom initialisation code in game objects.


583 changes: 273 additions & 310 deletions package-lock.json

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions package.json.template
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@
"description": "A simple engine for Javascript games",
"main": "Engine.js",
"dependencies": {
"webpack": "^4.32.2",
"uuid": "^3.3.2",
"webpack-cli": "^3.3.2"
"webpack": "^4.41.0",
"uuid": "^3.3.3",
"webpack-cli": "^3.3.9"
},
"devDependencies": {
"clean-webpack-plugin": "^3.0.0",
"html-webpack-plugin": "^3.2.0",
"webpack-merge": "^4.2.1"
"webpack-merge": "^4.2.2"
},
"scripts": {
"test": "echo \"No tests specified\"",
Expand Down
17 changes: 14 additions & 3 deletions src/engine/engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { GamepadHandler } from '../ui/gamepad';
import { partition } from '../lib/partition';
import { ImageService } from '../utils/image';
import { TimerSystem } from '../lib/timer';
import { Formatter } from '../lib/format';

class GameConfiguration {
constructor(userConfiguration, userLifecycle) {
Expand Down Expand Up @@ -48,11 +49,13 @@ class GameConfiguration {
class Engine {
constructor(userConfig, userLifecycle) {
this.id = 'ENGINE';
this.eventSystem = new Reactor();
this.audioSystem = new Audio();
this.timers = new TimerSystem(this);
this.formatter = new Formatter();
this.configuration = new GameConfiguration(userConfig, userLifecycle);
this.config = this.configuration.config;
this.debug = this.config.debugEngine || false;
this.eventSystem = new Reactor(this.debug);
this.audioSystem = new Audio();
this.timers = new TimerSystem(this, this.debug);
this.images = new ImageService();
this.eventSystem.registerEvent(this.id);
this.eventSystem.addEventListener(this.id, this.config.game.eventListener.bind(this, this));
Expand Down Expand Up @@ -167,6 +170,14 @@ Engine.prototype.registerObject = function(gameObject) {
return true;
}

Engine.prototype.deleteObjectById = function(id) {
const obj = this.getObjectById(id);
if (obj) {
obj.disposable = true;
}
return obj && obj.disposable == true;
}

Engine.prototype.getObjectById = function(id) {
const objs = this.getObjectsById(id);
return (objs && objs.length > 0) ? objs[0] : undefined;
Expand Down
1 change: 1 addition & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export * from './lib/2d';
export * from './lib/3d';
export * from './lib/audio';
export * from './lib/events';
export * from './lib/format';
export * from './lib/fsm';
export * from './lib/math';
export * from './lib/partition';
Expand Down
22 changes: 19 additions & 3 deletions src/lib/events.js
Original file line number Diff line number Diff line change
@@ -1,40 +1,56 @@
// from: https://stackoverflow.com/questions/15308371/custom-events-model-without-using-dom-events-in-javascript

import { Logger } from '../lib/logger';

class Event {
constructor(name){
constructor(name, debug){
this.name = name;
this.debug = debug || false;
this.callbacks = [];
this.logger = debug ? new Logger('Event') : undefined;
}
}

Event.prototype.registerCallback = function(callback){
this.callbacks.push(callback);
this.logger && this.logger.logAction(`callback pushed: ${this.callbacks.length} registered.`);
}

class Reactor{
constructor() {
constructor(debug) {
this.debug = debug || false;
this.events = {};
this.logger = debug ? new Logger('Reactor') : undefined;
}
}

Reactor.prototype.registerEvent = function(eventName){
var event = new Event(eventName);
const event = new Event(eventName);
this.events[eventName] = event;
this.logger && this.logger.logAction(`event registered: ${eventName}`);
};

Reactor.prototype.dispatchEvent = function(eventName, eventArgs){
const self = this;
let evCount = 0;
this.events[eventName].callbacks.forEach(function(callback){
evCount += 1;
callback(eventArgs);
self.logger && self.logger.logAction(`callback triggered for ${eventName} with args: ${eventArgs ? eventArgs : 'none'}`);
});
self.logger && self.logger.logAction(`event dispatched: ${eventName} - ${evCount} callback(s) triggered`);
};

Reactor.prototype.deRegisterEvent = function(eventName) {
if (this.events[eventName]) {
delete this.events[eventName];
}
this.logger && this.logger.logAction(`event de-registered: ${eventName}`);
};

Reactor.prototype.addEventListener = function(eventName, callback){
this.events[eventName].registerCallback(callback);
this.logger && this.logger.logAction(`eventListener callback registered: ${eventName}`);
};

export {
Expand Down
44 changes: 44 additions & 0 deletions src/lib/format.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Formatting helper functions

const leftPad = function(unpadded, char, maxLen) {
// don't rely on implicit conversion
const str = !isNaN(unpadded) ? unpadded.toString() : unpadded;
// protect against negative padding length
const padLen = ((maxLen || str.length) - str.length < 0) ? 0 : (maxLen || str.length) - str.length;
const padChars = char.repeat(padLen);
return `${padChars}${str}`;
};

const time = function(dateVal, includeMillis) {
const when = dateVal || new Date();
const hh = leftPad(when.getHours(), '0', 2);
const mm = leftPad(when.getMinutes(), '0', 2);
const ss = leftPad(when.getSeconds(), '0', 2);
const mi = leftPad(when.getMilliseconds(), '0', 3);
return `${hh}:${mm}:${ss}${includeMillis ? `.${mi}` : ''}`;
};

const date = function(dateVal) {
const when = dateVal || new Date();
const yyyy = when.getFullYear();
const mm = leftPad(when.getMonth(), '0', 2);
const dd = leftPad(when.getDate(), '0', 2);
return `${yyyy}-${mm}-${dd}`;
}

const dateTime = function(dateVal, includeMillis) {
return `${date(dateVal)} ${time(dateVal, includeMillis)}`;
};

class Formatter {
constructor() {
this.leftPad = leftPad;
this.formatTime = time;
this.formatDate = date;
this.formatDateTime = dateTime;
}
};

export {
Formatter
};
21 changes: 21 additions & 0 deletions src/lib/logger.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// A _really_ basic logger class for debugging ops
// Figured it'd be easier to use other logging targets/libraries via a single provider class

import { Formatter } from '../lib/format';

class Logger {
constructor(parent) {
this._parent = parent;
this.formatter = new Formatter();
}
}

Logger.prototype.logAction = function(logMessage) {
const now = new Date();
const withMillis = true;
console.log(`${this.formatter.formatTime(now, withMillis)} > ${this._parent}: ${logMessage}`);
}

export {
Logger
};
24 changes: 21 additions & 3 deletions src/lib/timer.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { partition } from './partition';
import { Logger } from './logger';

class Timer {
constructor(host, timerName, timeoutMs, intervalMs, triggerFn, fnArg) {
constructor(host, timerName, timeoutMs, intervalMs, triggerFn, fnArg, debug) {
this._name = timerName;
this._interval = intervalMs;
this._timeout = timeoutMs;
Expand All @@ -15,6 +16,7 @@ class Timer {
this._isCancelled = false;
this._trigger = () => {
}
this._logger = (debug ? new Logger('Timer') : undefined);
}
get name() {
return this._name;
Expand All @@ -28,6 +30,8 @@ Timer.prototype.tick = function() {
if (!(this._isPaused || this._isCancelled) && this._triggerFn) {
this._lastRun = Date.now();
this._triggerFn(this._triggerArg);
// Logging is a slow operation - only uncomment in times of desperation!
// this._logger && this._logger.logAction(`tick: ${this._name} (id: ${this._id})`);
}
}

Expand All @@ -39,6 +43,7 @@ Timer.prototype.start = function() {
this._id = setTimeout(this.tick.bind(this), this._timeout);
}
this._lastRun = Date.now();
this._logger && this._logger.logAction(`started: ${this._name} (id: ${this._id})`);
}

Timer.prototype.pause = function() {
Expand All @@ -48,38 +53,47 @@ Timer.prototype.pause = function() {
clearInterval(this._id);
}
this._isPaused = true;
this._logger && this._logger.logAction(`paused: ${this._name} (id: ${this._id})`);
}

Timer.prototype.cancel = function() {
const id = this._id;
if (this._timeout) clearTimeout(id);
if (this._interval) clearInterval(id);
this._isCancelled = true;
this._logger && this._logger.logAction(`cancelled: ${this._name} (id: ${this._id})`);
}


class TimerSystem {
constructor(host) {
constructor(host, debug) {
this._debug = debug || false;
this._host = host;
this._timers = [];
this._logger = (debug ? new Logger('TimerSystem') : undefined);
}
}

TimerSystem.prototype.cleanUp = function() {
const before = this._timers.length;
this._timers = partition(this._timers, function(timer){ return !timer._isCancelled })[0];
this._logger && this._logger.logAction(`cleanUp: before: ${before} after: ${this._timers.length}`);
}

TimerSystem.prototype.exists = function(timerName) {
const check = this._timers.filter(function(timer) { return timer.name == timerName });
this._logger && this._logger.logAction(`exists: ${timerName} ? ${check.length > 0}`);
return check.length > 0;
}

TimerSystem.prototype.add = function(timerName, timeoutMs, intervalMs, triggerFn, fnArg) {
if (!this.exists(timerName)) {
const timer = new Timer(this, timerName, timeoutMs, intervalMs, triggerFn, fnArg);
const timer = new Timer(this, timerName, timeoutMs, intervalMs, triggerFn, fnArg, this._debug);
this._timers.push(timer);
this._logger && this._logger.logAction(`added ${timerName} (timeout: ${timeoutMs}ms interval: ${intervalMs}ms trigger: ${triggerFn ? true : false} args: ${fnArg ? true : false})`);
return true;
}
this._logger && this._logger.logAction(`add failed: ${timerName} already exists`);
return false;
}

Expand All @@ -92,6 +106,7 @@ TimerSystem.prototype.start = function(timerName) {
}

TimerSystem.prototype.startAll = function() {
this._logger && this._logger.logAction(`starting all timers`);
for (const t in this._timers) {
this._timers[t].start();
}
Expand All @@ -106,6 +121,7 @@ TimerSystem.prototype.pause = function(timerName) {
}

TimerSystem.prototype.pauseExcept = function(timerName) {
this._logger && this._logger.logAction(`pause all timers EXCEPT ${timerName}`);
for (const t in this._timers) {
if (this._timers[t].name != timerName) {
this._timers[t].pause();
Expand All @@ -114,6 +130,7 @@ TimerSystem.prototype.pauseExcept = function(timerName) {
}

TimerSystem.prototype.pauseAll = function() {
this._logger && this._logger.logAction(`pausing all timers`);
for (const t in this._timers) {
this._timers[t].pause();
}
Expand All @@ -129,6 +146,7 @@ TimerSystem.prototype.cancel = function(timerName) {
}

TimerSystem.prototype.cancelAll = function() {
this._logger && this._logger.logAction(`cancelling all timers`);
for (const t in this._timers) {
this._timers[t].cancel();
}
Expand Down
18 changes: 14 additions & 4 deletions src/model/gameObject.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ export class GameObject {
this.ready = false;
this.disposable = false;
this.drawable = false;
this.id = uuidv4();
this.id = uuidv4();
this.maxStartupMillis = 500; // <- tune if this.initDone is being triggered too soon or too late
this.engine.eventSystem.registerEvent(`${this.id}-Loaded`);
this.engine.eventSystem.addEventListener(`${this.id}-Loaded`, this.init.bind(this));
this.conf = conf;
Expand Down Expand Up @@ -65,8 +66,11 @@ export class GameObject {
}
}

GameObject.prototype.eventListener = function (thisObj, evt) {
console.log(`${this.id} GameObject eventListener captured event for obj: ${thisObj.id} with args: ${evt}`);
GameObject.prototype.eventListener = function (thisObj, evt) {
const thisId = this.id;
const otherId = thisObj.id == thisId ? 'itself' : `obj id:${otherId}`;
const eventDescription = `${evt.target} -> ${evt.action}`;
console.log(`GameObject eventListener on ${thisId} (type ${this.type}) caught an event of type ${eventDescription} intended for ${otherId}. Maybe consider implementing a handler in the descendant object's class.`);
}

GameObject.prototype.rotate = function(degrees) {
Expand Down Expand Up @@ -135,11 +139,17 @@ GameObject.prototype.init = function() {
this.engine.eventSystem.registerEvent(`${this.id}FSM`);
this.engine.eventSystem.addEventListener(`${this.id}FSM`, this.fsm.eventListener.bind(this.fsm, this));
}
this.engine.eventSystem.deRegisterEvent(`${this.id}-Loaded`);
this.engine.timers.add(`${this.id}-InitDone`, null, this.maxStartupMillis, this.initDone.bind(this), this);
this.engine.timers.start(`${this.id}-InitDone`);
this.ready = true;
this.canDraw = true;
}

GameObject.prototype.initDone = function() {
this.engine.eventSystem.deRegisterEvent(`${this.id}-Loaded`);
this.engine.timers.cancel(`${this.id}-InitDone`);
}

GameObject.prototype.scaleWidth = function(dim) {
if (this.scale && this.scale.x) {
return dim * this.scale.x;
Expand Down

0 comments on commit a73fd01

Please sign in to comment.