├── .gitignore ├── extraParams.hxml ├── .gitattributes ├── demo └── basic │ ├── build-basic.hxml │ ├── bin │ ├── index.html │ └── main.js │ └── src │ └── Game.hx ├── src └── edge │ ├── ViewData.hx │ ├── IComponent.hx │ ├── ISystem.hx │ ├── core │ ├── ISystemProcess.hx │ ├── NodeSystem.hx │ ├── NodeSystemIterator.hx │ └── macro │ │ ├── BuildComponent.hx │ │ ├── BuildSystem.hx │ │ ├── Macros.hx │ │ └── BuildSystemProcess.hx │ ├── View.hx │ ├── World.hx │ ├── Engine.hx │ ├── Entity.hx │ └── Phase.hx ├── bin └── test.html ├── submit.sh ├── haxelib.json ├── LICENSE ├── README.md └── test └── TestAll.hx /.gitignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /extraParams.hxml: -------------------------------------------------------------------------------- 1 | -lib thx.core -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | **/bin/* linguist-vendored 2 | -------------------------------------------------------------------------------- /demo/basic/build-basic.hxml: -------------------------------------------------------------------------------- 1 | -cp src 2 | -js bin/main.js 3 | -lib edge 4 | -lib minicanvas 5 | -main Game 6 | -dce full -------------------------------------------------------------------------------- /src/edge/ViewData.hx: -------------------------------------------------------------------------------- 1 | package edge; 2 | 3 | typedef ViewData = { 4 | entity : Entity, 5 | data : T 6 | } -------------------------------------------------------------------------------- /src/edge/IComponent.hx: -------------------------------------------------------------------------------- 1 | package edge; 2 | 3 | @:autoBuild(edge.core.macro.BuildComponent.complete()) 4 | interface IComponent {} -------------------------------------------------------------------------------- /bin/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Test Edge 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /submit.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | rm edge.zip 4 | zip -r edge.zip src test demo extraParams.hxml haxelib.json LICENSE README.md -x "*/\.*" 5 | haxelib submit edge.zip 6 | -------------------------------------------------------------------------------- /demo/basic/bin/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Edge - Basic 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/edge/ISystem.hx: -------------------------------------------------------------------------------- 1 | package edge; 2 | 3 | import edge.core.ISystemProcess; 4 | 5 | @:autoBuild(edge.core.macro.BuildSystem.complete()) 6 | interface ISystem { 7 | private var __process__ : ISystemProcess; 8 | } -------------------------------------------------------------------------------- /src/edge/core/ISystemProcess.hx: -------------------------------------------------------------------------------- 1 | package edge.core; 2 | 3 | interface ISystemProcess { 4 | public function update(engine : Engine, delta : Float) : Bool; 5 | public function addEntity(entity : Entity) : Void; 6 | public function removeEntity(entity : Entity) : Void; 7 | } 8 | -------------------------------------------------------------------------------- /src/edge/core/NodeSystem.hx: -------------------------------------------------------------------------------- 1 | package edge.core; 2 | 3 | class NodeSystem { 4 | public var system(default, null) : ISystem; 5 | public var next(default, null) : NodeSystem; 6 | public var prev(default, null) : NodeSystem; 7 | 8 | public function new(system : ISystem) { 9 | this.system = system; 10 | } 11 | } -------------------------------------------------------------------------------- /src/edge/core/NodeSystemIterator.hx: -------------------------------------------------------------------------------- 1 | package edge.core; 2 | 3 | class NodeSystemIterator { 4 | var node : NodeSystem; 5 | public function new(node : NodeSystem) { 6 | this.node = node; 7 | } 8 | 9 | public function hasNext() 10 | return null != node; 11 | 12 | public function next() { 13 | var system = node.system; 14 | node = node.next; 15 | return system; 16 | } 17 | } -------------------------------------------------------------------------------- /haxelib.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "edge", 3 | "url": "https://github.com/fponticelli/edge", 4 | "license": "MIT", 5 | "classPath": "src", 6 | "tags": ["entity-system", "gaming", "cross"], 7 | "description": "Entity system for Haxe.", 8 | "releasenote": "Fixes and addressed changes in thx.core", 9 | "version": "0.7.0", 10 | "contributors": ["fponticelli"], 11 | "dependencies": { 12 | "thx.core": "" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/edge/View.hx: -------------------------------------------------------------------------------- 1 | package edge; 2 | 3 | class View { 4 | public var count(default, null) : Int; 5 | var map : Map; 6 | public function new() { 7 | map = new Map(); 8 | count = 0; 9 | } 10 | 11 | // TODO optimize 12 | public function iterator() : Iterator> { 13 | var keys = map.keys(), 14 | holder = { entity : null, data : null }; 15 | return { 16 | hasNext : function() { 17 | return keys.hasNext(); 18 | }, 19 | next : function() { 20 | var key = keys.next(); 21 | holder.entity = key; 22 | holder.data = map.get(key); 23 | return holder; 24 | } 25 | }; 26 | } 27 | 28 | function tryAdd(entity : Entity, data : T) { 29 | if(map.exists(entity)) return false; 30 | map.set(entity, data); 31 | count++; 32 | return true; 33 | } 34 | 35 | function tryRemove(entity : Entity) : T { 36 | var o = map.get(entity); 37 | if(null == o) return null; 38 | map.remove(entity); 39 | count--; 40 | return o; 41 | } 42 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Franco Ponticelli 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /src/edge/World.hx: -------------------------------------------------------------------------------- 1 | package edge; 2 | 3 | import thx.Timer; 4 | 5 | class World { 6 | public var frame(default, null) : Phase; 7 | public var physics(default, null) : Phase; 8 | public var render(default, null) : Phase; 9 | public var engine(default, null) : Engine; 10 | public var delta(default, null) : Float; 11 | public var running(default, null) : Bool; 12 | 13 | var schedule : (Float -> Void) -> (Void -> Void); 14 | var cancel : Void -> Void; 15 | var remainder : Float; 16 | public function new(?delta : Float = 16, ?schedule : (Float -> Void) -> (Void -> Void)) { 17 | engine = new Engine(); 18 | frame = engine.createPhase(); 19 | physics = engine.createPhase(); 20 | render = engine.createPhase(); 21 | remainder = 0; 22 | running = false; 23 | this.delta = delta; 24 | this.schedule = null != schedule ? schedule : Timer.frame; 25 | } 26 | 27 | public function start() { 28 | if(running) return; 29 | running = true; 30 | cancel = schedule(run); 31 | } 32 | 33 | function run(t : Float) { 34 | frame.update(t); 35 | var dt = t + remainder; 36 | while(dt > delta) { 37 | dt -= delta; 38 | physics.update(delta); 39 | } 40 | remainder = dt; 41 | render.update(t); 42 | } 43 | 44 | public function stop() { 45 | if(!running) return; 46 | running = false; 47 | cancel(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /demo/basic/src/Game.hx: -------------------------------------------------------------------------------- 1 | import edge.*; 2 | import minicanvas.MiniCanvas; 3 | 4 | class Game { 5 | public static var width(default, null) = 200; 6 | public static var height(default, null) = 200; 7 | 8 | public static function main() { 9 | var mini = MiniCanvas.create(width, height) 10 | .display("basic example"), 11 | world = new World(); 12 | 13 | for(i in 0...300) 14 | world.engine.create([ 15 | new Position( 16 | Math.random() * width, 17 | Math.random() * height), 18 | new Velocity( 19 | Math.random() * 2 - 1, 20 | Math.random() * 2 - 1) 21 | ]); 22 | 23 | for(i in 0...20) 24 | world.engine.create([ 25 | new Position( 26 | Math.random() * width, 27 | Math.random() * height) 28 | ]); 29 | 30 | world.physics.add(new UpdateMovement()); 31 | 32 | world.render.add(new RenderDots(mini)); 33 | 34 | world.start(); 35 | } 36 | } 37 | 38 | class Position implements IComponent { 39 | var x : Float; 40 | var y : Float; 41 | } 42 | 43 | class Velocity implements IComponent { 44 | var vx : Float; 45 | var vy : Float; 46 | } 47 | 48 | class RenderDots implements ISystem { 49 | var mini : MiniCanvas; 50 | public function new(mini : MiniCanvas) 51 | this.mini = mini; 52 | 53 | function before() 54 | mini.clear(); 55 | 56 | function update(pos : Position) 57 | mini.dot(pos.x, pos.y, 2, 0x000000FF); 58 | } 59 | 60 | class UpdateMovement implements ISystem { 61 | function update(pos : Position, vel : Velocity) { 62 | var dx = pos.x + vel.vx, 63 | dy = pos.y + vel.vy; 64 | if(dx <= 0 && vel.vx < 0 || dx >= Game.width && vel.vx > 0) 65 | vel.vx = -vel.vx; 66 | else 67 | pos.x = dx; 68 | if(dy <= 0 && vel.vy < 0 || dy >= Game.height && vel.vy > 0) 69 | vel.vy = -vel.vy; 70 | else 71 | pos.y = dy; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/edge/Engine.hx: -------------------------------------------------------------------------------- 1 | package edge; 2 | 3 | import edge.Entity; 4 | 5 | @:access(edge.Entity) 6 | @:access(edge.ISystem) 7 | class Engine { 8 | var mapEntities : Map; 9 | var listPhases : Array; 10 | public function new() { 11 | mapEntities = new Map(); 12 | listPhases = []; 13 | } 14 | 15 | public function create(?components : Array<{}>) { 16 | var entity = new Entity(this, components); 17 | mapEntities.set(entity, true); 18 | matchSystems(entity); 19 | return entity; 20 | } 21 | 22 | public function clear() 23 | for(entity in mapEntities.keys()) 24 | remove(entity); 25 | 26 | function remove(entity : Entity) { 27 | eachSystem(function(system) system.__process__.removeEntity(entity)); 28 | mapEntities.remove(entity); 29 | entity.engine = null; 30 | } 31 | 32 | public function entities() 33 | return mapEntities.keys(); 34 | 35 | public function createPhase() { 36 | var phase = new Phase(this); 37 | listPhases.push(phase); 38 | return phase; 39 | } 40 | 41 | public function phases() 42 | return listPhases.iterator(); 43 | 44 | public function eachSystem(f : ISystem -> Void) { 45 | for(phase in listPhases) 46 | for(system in phase.systems()) 47 | f(system); 48 | } 49 | 50 | // private methods 51 | function addSystem(system : ISystem) { 52 | eachSystem( 53 | function(s) 54 | if(s == system) 55 | throw 'System "$system" already exists in Engine'); 56 | for(entity in mapEntities.keys()) 57 | match(entity, system); 58 | } 59 | 60 | // TODO, remove all together, not one at the time 61 | function removeSystem(system : ISystem) 62 | for(entity in mapEntities.keys()) 63 | system.__process__.removeEntity(entity); 64 | 65 | function updateSystem(system : ISystem, t : Float) 66 | return system.__process__.update(this, t); 67 | 68 | function matchSystems(entity : Entity) 69 | eachSystem(function(system) match(entity, system)); 70 | 71 | inline function match(entity : Entity, system : ISystem) 72 | system.__process__.addEntity(entity); 73 | } 74 | -------------------------------------------------------------------------------- /src/edge/Entity.hx: -------------------------------------------------------------------------------- 1 | package edge; 2 | 3 | using thx.Arrays; 4 | using thx.Functions; 5 | 6 | @:access(edge.Engine) 7 | class Entity { 8 | var map : Map; 9 | public var engine(default, null) : Engine; 10 | function new(engine : Engine, ?components : Array<{}>) { 11 | this.engine = engine; 12 | this.map = new Map(); 13 | if(null != components) 14 | addMany(components); 15 | } 16 | 17 | public function add(component : {}) { 18 | if(null == engine) return; 19 | _add(component); 20 | engine.matchSystems(this); 21 | } 22 | 23 | public function addMany(components : Array<{}>) { 24 | if(null == engine) return; 25 | components.map.fn(_add(_)); 26 | engine.matchSystems(this); 27 | } 28 | 29 | public function destroy() { 30 | if(null == engine) return; 31 | engine.remove(this); 32 | map = new Map(); 33 | } 34 | 35 | inline public function get(type : Class): Null 36 | return cast map.get(Type.getClassName(type)); 37 | 38 | public function exists(component : {}) 39 | return existsType(Type.getClass(component)); 40 | 41 | public function existsType(type : Class<{}>) 42 | return map.exists(Type.getClassName(type)); 43 | 44 | public function remove(component : {}) { 45 | _remove(component); 46 | engine.matchSystems(this); 47 | } 48 | 49 | public function removeMany(components : Array<{}>) { 50 | components.map.fn(_remove(_)); 51 | engine.matchSystems(this); 52 | } 53 | 54 | public function removeType(type : Class<{}>) { 55 | _removeTypeName(Type.getClassName(type)); 56 | engine.matchSystems(this); 57 | } 58 | 59 | public function removeTypes(types : Array>) { 60 | types.map.fn(_removeTypeName(Type.getClassName(_))); 61 | engine.matchSystems(this); 62 | } 63 | 64 | inline public function components() 65 | return map.iterator(); 66 | 67 | function _add(component : {}) { 68 | var type = key(component); 69 | if(map.exists(type)) 70 | remove(map.get(type)); 71 | map.set(type, component); 72 | } 73 | 74 | function _remove(component : {}) { 75 | var type = key(component); 76 | _removeTypeName(type); 77 | } 78 | 79 | function _removeTypeName(type : String) 80 | map.remove(type); 81 | 82 | function key(component : {}) { 83 | var t : Class = Type.getClass(component), 84 | s = Type.getSuperClass(t); 85 | while(s != null && s != edge.IComponent) { 86 | t = s; 87 | s = Type.getSuperClass(t); 88 | } 89 | return Type.getClassName(t); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/edge/core/macro/BuildComponent.hx: -------------------------------------------------------------------------------- 1 | package edge.core.macro; 2 | 3 | import haxe.macro.Context; 4 | import haxe.macro.Expr; 5 | import haxe.macro.Type; 6 | using thx.macro.MacroFields; 7 | 8 | import edge.core.macro.Macros.*; 9 | 10 | class BuildComponent { 11 | macro public static function complete() : Array { 12 | var fields = Context.getBuildFields(), 13 | cls = Context.getLocalClass().get(); 14 | makeVarsPublic(fields); 15 | injectToString(fields, cls); 16 | injectConstructor(fields, cls); 17 | return fields; 18 | } 19 | 20 | static function injectConstructor(fields : Array, type : ClassType) { 21 | if(hasField(fields, "new")) return; 22 | var args = getVarAsFunctionArgs(fields), 23 | init = args 24 | .map(function(arg) return arg.name) 25 | .map(function(name) return macro this.$name = $i{name}); 26 | 27 | var ancestor:Null = getAncestor(type), 28 | ancestorArgs:Array = []; 29 | 30 | if (ancestor != null) { 31 | do { 32 | //Reverse concat, to make sure ancestor arguments are first 33 | var funArgs = getClassVarAsFunctionArgs(ancestor.fields.get()); 34 | args = funArgs.concat(args); 35 | ancestorArgs = funArgs.concat(ancestorArgs); 36 | type = ancestor; 37 | } while((ancestor = getAncestor(type)) != null); 38 | 39 | var a = ancestorArgs.map(function(arg) return macro $i{arg.name}); 40 | init.unshift(macro super($a{a})); 41 | } 42 | fields.push(createFunctionField("new", args, macro $b{init})); 43 | } 44 | 45 | static function injectToString(fields : Array, type : ClassType) { 46 | if(hasField(fields, "toString")) return; 47 | var args = getVarAsFunctionArgs(fields), 48 | access = [APublic]; 49 | 50 | var ancestor:Null = getAncestor(type); 51 | if (ancestor != null) { 52 | access.push(AOverride); 53 | do { 54 | //Reverse concat, to make sure ancestor arguments are first 55 | args = getClassVarAsFunctionArgs(ancestor.fields.get()).concat(args); 56 | type = ancestor; 57 | } while((ancestor = getAncestor(type)) != null); 58 | } 59 | 60 | var cls = clsName().split(".").pop(), 61 | params = args 62 | .map(function(arg) return '${arg.name}=$' + arg.name) 63 | .join(","), 64 | s = 'return \'$cls($params)\''; 65 | fields.push(createFunctionField( 66 | "toString", 67 | [], 68 | macro : String, 69 | Context.parse(s, Context.currentPos()), 70 | access) 71 | ); 72 | } 73 | } -------------------------------------------------------------------------------- /src/edge/Phase.hx: -------------------------------------------------------------------------------- 1 | package edge; 2 | 3 | import edge.core.NodeSystem; 4 | import edge.core.NodeSystemIterator; 5 | 6 | @:access(edge.Engine.addSystem) 7 | @:access(edge.Engine.removeSystem) 8 | @:access(edge.Engine.updateSystem) 9 | @:access(edge.core.NodeSystem) 10 | class Phase { 11 | var first : NodeSystem; 12 | var last : NodeSystem; 13 | var mapSystem : Map; 14 | var mapType : Map; 15 | var engine : Engine; 16 | var phases : Array; 17 | public var enabled : Bool; 18 | public function new(engine : Engine) { 19 | this.engine = engine; 20 | mapSystem = new Map(); 21 | mapType = new Map(); 22 | phases = []; 23 | enabled = true; 24 | } 25 | 26 | public function add(system : ISystem) { 27 | remove(system); 28 | var node = createNode(system); 29 | if(null == first) { 30 | first = node; 31 | last = node; 32 | } else { 33 | node.prev = last; 34 | last.next = node; 35 | last = node; 36 | } 37 | } 38 | 39 | public function createPhase() { 40 | var phase = engine.createPhase(); 41 | phases.push(phase); 42 | return phase; 43 | } 44 | 45 | public function clearSystems() 46 | for(system in systems()) 47 | remove(system); 48 | 49 | public function insertBefore(ref : ISystem, system : ISystem) { 50 | var noderef = mapSystem.get(ref); 51 | if(null == noderef) 52 | throw 'Phase.insertBefore: unable to find $ref system'; 53 | var node = createNode(system); 54 | if(noderef == first) { 55 | node.next = noderef; 56 | noderef.prev = node; 57 | first = node; 58 | } else { 59 | var prev = noderef.prev; 60 | prev.next = node; 61 | node.prev = prev; 62 | node.next = noderef; 63 | noderef.prev = node; 64 | } 65 | } 66 | 67 | public function insertAfter(ref : ISystem, system : ISystem) { 68 | var noderef = mapSystem.get(ref); 69 | if(null == noderef) 70 | throw 'Phase.insertAfter: unable to find $ref system'; 71 | var node = createNode(system); 72 | if(noderef == last) { 73 | node.prev = noderef; 74 | noderef.next = node; 75 | last = node; 76 | } else { 77 | var next = noderef.next; 78 | next.prev = node; 79 | node.next = next; 80 | node.prev = noderef; 81 | noderef.next = node; 82 | } 83 | } 84 | 85 | public function remove(system : ISystem) { 86 | var node = mapSystem.get(system); 87 | mapType.remove(key(system)); 88 | if(null == node) 89 | return; 90 | engine.removeSystem(system); 91 | mapSystem.remove(system); 92 | if(node == first && node == last) { 93 | first = last = null; 94 | } else if(node == first) { 95 | first = node.next; 96 | node.next.prev = null; 97 | } else if(node == last) { 98 | last = node.prev; 99 | node.prev.next = null; 100 | } else { 101 | node.prev.next = node.next; 102 | node.next.prev = node.prev; 103 | } 104 | } 105 | 106 | public function removeType(cls : Class) { 107 | var system = mapType.get(Type.getClassName(cls)); 108 | if(null == system) 109 | throw 'type system ${Type.getClassName(cls)} is not included in this Phase'; 110 | return remove(system); 111 | } 112 | 113 | public function systems() 114 | return new NodeSystemIterator(first); 115 | 116 | public function update(t : Float) { 117 | if(!enabled) return; 118 | var result; 119 | for(system in systems()) { 120 | result = engine.updateSystem(system, t); 121 | if(!result) return; 122 | } 123 | for(phase in phases) { 124 | phase.update(t); 125 | } 126 | } 127 | 128 | function createNode(system : ISystem) { 129 | var node = new NodeSystem(system); 130 | mapSystem.set(system, node); 131 | mapType.set(key(system), system); 132 | engine.addSystem(system); 133 | return node; 134 | } 135 | 136 | function key(system : ISystem) 137 | return Type.getClassName(Type.getClass(system)); 138 | } 139 | -------------------------------------------------------------------------------- /src/edge/core/macro/BuildSystem.hx: -------------------------------------------------------------------------------- 1 | package edge.core.macro; 2 | 3 | import haxe.macro.Context; 4 | import haxe.macro.Expr; 5 | import haxe.macro.ExprTools; 6 | import haxe.macro.Type; 7 | using haxe.macro.TypeTools; 8 | using thx.macro.MacroFields; 9 | import edge.core.macro.Macros.*; 10 | 11 | class BuildSystem { 12 | public inline static var PROCESS_SUFFIX = "_SystemProcess"; 13 | 14 | macro public static function complete() : Array { 15 | var fields = Context.getBuildFields(), 16 | type = Context.getLocalClass().get(); 17 | checkUpdate(fields); 18 | injectToString(type, fields); 19 | injectConstructor(type, fields); 20 | makePublic(fields, "engine"); 21 | makePublic(fields, "entity"); 22 | makePublic(fields, "timeDelta"); 23 | makePublic(fields, "before"); 24 | injectSystemProcess(fields, Context.getLocalClass()); 25 | return fields; 26 | } 27 | 28 | static function injectSystemProcess(fields : Array, cls : Ref) { 29 | var system = cls.toString(), 30 | process = '$system${PROCESS_SUFFIX}'; 31 | 32 | BuildSystemProcess.createProcessType(system, process, fields); 33 | 34 | fields.push(createVarField("__process__", macro : edge.core.ISystemProcess)); 35 | 36 | appendExprToFieldFunction( 37 | findField(fields, "new"), 38 | Context.parse('__process__ = new $process(this)', Context.currentPos()) 39 | ); 40 | } 41 | 42 | static function injectToString(type : ClassType, fields : Array) { 43 | if(isFieldInHirearchy(type, "toString")) return; 44 | var cls = clsName(); 45 | fields.push(createFunctionField("toString", macro : String, macro return $v{cls})); 46 | } 47 | 48 | static function injectConstructor(type : ClassType, fields : Array) { 49 | if(hasField(fields, "new")) return; 50 | fields.push(createFunctionField( 51 | "new", 52 | isFieldInHirearchy(type, "new") ? macro super() : macro {} 53 | )); 54 | } 55 | 56 | static function checkUpdate(fields : Array) { 57 | var field = findField(fields, "update"); 58 | if(field == null) 59 | Context.error('${clsName()} doesn\'t contain a method `update`', Context.currentPos()); 60 | if(!field.isPublic()) 61 | field.access.push(APublic); 62 | if(field.isStatic()) 63 | Context.error('${clsName()}.update() cannot be static', Context.currentPos()); 64 | if(!field.isMethod()) 65 | Context.error('${clsName()}.update() must be method', Context.currentPos()); 66 | switch field.kind { 67 | case FFun(f): 68 | for(arg in f.args) { 69 | switch arg.type { 70 | case TPath(p): 71 | if(p.params.length > 0) 72 | Context.error('argument `${arg.name}` of ${clsName()}.update() cannot have type parameters', Context.currentPos()); 73 | var t = Context.getType(p.name).follow(); 74 | switch t { 75 | case TInst(s, _) if(s.toString() != "String"): 76 | // TODO, should we support enums? 77 | case _: 78 | Context.error('argument `${arg.name}` of ${clsName()}.update() is not a class instance', Context.currentPos()); 79 | } 80 | case _: 81 | Context.error('argument `${arg.name}` of ${clsName()}.update() is not a class instance', Context.currentPos()); 82 | } 83 | } 84 | var ret = f.ret; 85 | if(null == ret) { 86 | ret = macro : Void; 87 | } 88 | switch ret { 89 | case macro : Void: // change return type to Bool 90 | var exprs = [ 91 | changeReturnFromVoidToBool(f.expr), 92 | macro return true 93 | ]; 94 | f.expr = macro $b{exprs}; 95 | f.ret = macro : Bool; 96 | case macro : Bool: // you are good to go 97 | case _: Context.error('${clsName()}.update() return type is invalid: ${ret}', Context.currentPos()); 98 | } 99 | case _: 100 | } 101 | if(!fieldHasMeta(field, ":keep")) 102 | field.meta.push({ name : ":keep", pos : Context.currentPos() }); 103 | } 104 | 105 | static function changeReturnFromVoidToBool(expr) { 106 | return ExprTools.map(expr, function(e) { 107 | switch e.expr { 108 | case EReturn(v) if(v == null): 109 | return macro return true; 110 | case EReturn(v): 111 | return e; 112 | case ex: 113 | return changeReturnFromVoidToBool(e); 114 | }; 115 | }); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/edge/core/macro/Macros.hx: -------------------------------------------------------------------------------- 1 | package edge.core.macro; 2 | 3 | import haxe.macro.Context; 4 | import haxe.macro.Expr; 5 | import haxe.macro.Type; 6 | using haxe.macro.TypeTools; 7 | using thx.macro.MacroFields; 8 | import Type in RType; 9 | 10 | class Macros { 11 | public static function getVarAsFunctionArgs(fields : Array) : Array { 12 | return fields 13 | .map(function(field) return switch field.kind { 14 | case FVar(t, _) if(!field.isStatic()): 15 | { name : field.name, type : t, opt : null, value : null, meta : null } 16 | case _: 17 | null; 18 | }) 19 | .filter(function(field) return field != null); 20 | } 21 | 22 | public static function getClassVarAsFunctionArgs(fields : Array) : Array { 23 | return fields 24 | .map(function(field) return switch field.kind { 25 | case FVar(t, _): 26 | { name : field.name, type : field.type.follow().toComplexType(), opt : null, value : null, meta : null } 27 | case _: 28 | null; 29 | }) 30 | .filter(function(field) return field != null); 31 | } 32 | 33 | public static function createVarField(name : String, type : ComplexType) : Field { 34 | return { 35 | name: name, 36 | kind: FVar(type, null), 37 | pos: Context.currentPos() 38 | }; 39 | } 40 | 41 | public static function createFunctionField(name : String, ?args : Array, ?ret : ComplexType, ?expr : Expr, ?access : Array) : Field { 42 | return { 43 | name: name, 44 | access: null != access ? access : [APublic], 45 | kind: FFun({ 46 | ret : null != ret ? ret : macro : Void, 47 | expr : null != expr ? expr : macro {}, 48 | args : null != args ? args : [] 49 | }), 50 | pos: Context.currentPos() 51 | }; 52 | } 53 | 54 | public static function makeVarsPublic(fields : Array) { 55 | fields.map(function(field) switch field.kind { 56 | case FVar(_, _) if(!field.isPublic()): 57 | field.access.push(APublic); 58 | case _: 59 | }); 60 | } 61 | 62 | public static function makePublic(fields : Array, name : String) { 63 | var field = findField(fields, name); 64 | if(null == field) return; 65 | makeFieldPublic(field); 66 | } 67 | 68 | public static function makeFieldPublic(field : Field) { 69 | if(isPublic(field)) return; 70 | field.access.push(APublic); 71 | } 72 | 73 | public static function isPublic(field : Field) { 74 | for(a in field.access) 75 | if(RType.enumEq(a, APublic)) 76 | return true; 77 | return false; 78 | } 79 | 80 | public static function hasField(fields : Array, name : String) 81 | return null != findField(fields, name); 82 | 83 | public static function findField(fields : Array, name : String) { 84 | for(field in fields) 85 | if(field.name == name) 86 | return field; 87 | return null; 88 | } 89 | 90 | public static function findClassField(fields : Array, name : String) { 91 | for(field in fields) { 92 | if(field.name == name) 93 | return field; 94 | } 95 | return null; 96 | } 97 | 98 | public static function hasClassField(fields : Array, name : String) 99 | return findClassField(fields, name) != null; 100 | 101 | public static function isFieldInHirearchy(type : ClassType, name : String) : Bool { 102 | if(name == "new") { 103 | if(null != type.constructor) 104 | return true; 105 | } else { 106 | if(hasClassField(type.fields.get(), name)) 107 | return true; 108 | } 109 | var superClass = type.superClass; 110 | if(null == superClass) { 111 | return false; 112 | } 113 | return isFieldInHirearchy(superClass.t.get(), name); 114 | } 115 | 116 | public static function hasVarField(fields : Array, fieldName : String) { 117 | for(field in fields) 118 | if(field.name == fieldName && switch field.kind { 119 | case FVar(_, _): true; 120 | case _: false; 121 | }) 122 | return true; 123 | return false; 124 | } 125 | 126 | public static function hasFunField(fields : Array, fieldName : String) { 127 | for(field in fields) 128 | if(field.name == fieldName && switch field.kind { 129 | case FFun(_): true; 130 | case _: false; 131 | }) 132 | return true; 133 | return false; 134 | } 135 | 136 | public static function fieldHasMeta(field : Field, name : String) { 137 | if(field.meta == null) return false; 138 | for(meta in field.meta) 139 | if(meta.name == name) 140 | return true; 141 | return false; 142 | } 143 | 144 | inline public static function clsName() 145 | return Context.getLocalClass().toString(); 146 | 147 | public static function appendExprToFieldFunction(field : Field, expr : Expr) { 148 | switch field.kind { 149 | case FFun(o): 150 | var exprs = [o.expr, expr]; 151 | o.expr = macro $b{exprs}; 152 | case _: 153 | } 154 | } 155 | 156 | public static function fieldFunctionHasArguments(field : Field) { 157 | switch field.kind { 158 | case FFun(o): 159 | return o.args.length > 0; 160 | case _: 161 | return false; 162 | } 163 | } 164 | 165 | public static function fieldFunctionArguments(field : Field) { 166 | switch field.kind { 167 | case FFun(o): 168 | return o.args; 169 | case _: 170 | return null; 171 | } 172 | } 173 | 174 | public static function getAncestor(type : ClassType):Null 175 | { 176 | var c = type.superClass; 177 | if (c == null) return null; 178 | 179 | return c.t.get(); 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # edge 2 | 3 | Entity system for Haxe 4 | 5 | ## introduction 6 | 7 | *edge* works on the following principles: 8 | 9 | * an `Entity` is a collection of components with no additional logic. 10 | * a `Component` is an instance of any type of Class. You can use anything as a component except for anonymous objects and primitive types. 11 | * a `Component` is a data object with no logic. If you want to put some logic in them, do it at your own risk. You have been warned. 12 | * a `System` manages a portion of the application logic and it is responsible for reading and writing from and to the components as needed. 13 | * A `System` is required to have at least the `update()` method defined. Update can take zero or more arguments. Arguments types should only be components. 14 | * the `Engine` manages the entities, their pairing with the systems and the application phases. 15 | * a `Phase` is a collection of systems that need to be processed sometime in the future. 16 | * when a `Phase` is updated, `Engine` invokes the `update()` method of each `System` included in `Phase`. Only systems that are paired with at least one entity will be triggered, and once for each entity that matches `update`. A System whose `update` method has no arguments will be invoked on each update once. 17 | * `World` is a general case implementation to add a scheduler based on the concept of frame with the `rendering` and `physics` phases. If you plan to do sophisticated things with your loops/phases, you might consider writing an alternative implementation of `World`. 18 | 19 | ## install 20 | 21 | For the official release: 22 | 23 | ```bash 24 | haxelib install edge 25 | ``` 26 | 27 | For the cutting-edge/dev-version: 28 | 29 | ```bash 30 | haxelib git edge https://github.com/fponticelli/edge.git 31 | ``` 32 | 33 | ## example 34 | 35 | In the example below we create a bunch of entities some with both `Position` and `Velocity` and some with only `Position`. The system `UpdateMovement` will only affect the entities with both components, while `RenderingDots` will be applied to all the entities in this context. 36 | 37 | `RenderingBackground` will clear the canvas on every frame before any other rendering operation. Note that it will only be invoked once per frame and it doesn't rely on the presence of any entity (`update()` takes no argument). 38 | 39 | See the [example in action](https://rawgit.com/fponticelli/edge/master/demo/basic/bin/index.html). 40 | 41 | ```haxe 42 | import edge.*; 43 | import minicanvas.MiniCanvas; 44 | 45 | class Game { 46 | public static var width(default, null) = 200; 47 | public static var height(default, null) = 200; 48 | 49 | public static function main() { 50 | var mini = MiniCanvas.create(width, height) 51 | .display("basic example"), 52 | world = new World(); 53 | 54 | for(i in 0...300) 55 | world.engine.create([ 56 | new Position( 57 | Math.random() * width, 58 | Math.random() * height), 59 | new Velocity( 60 | Math.random() * 2 - 1, 61 | Math.random() * 2 - 1) 62 | ]); 63 | 64 | for(i in 0...20) 65 | world.engine.create([ 66 | new Position( 67 | Math.random() * width, 68 | Math.random() * height) 69 | ]); 70 | 71 | world.physics.add(new UpdateMovement()); 72 | 73 | world.render.add(new RenderDots(mini)); 74 | 75 | world.start(); 76 | } 77 | } 78 | 79 | class Position implements IComponent { 80 | var x : Float; 81 | var y : Float; 82 | } 83 | 84 | class Velocity implements IComponent { 85 | var vx : Float; 86 | var vy : Float; 87 | } 88 | 89 | class RenderDots implements ISystem { 90 | var mini : MiniCanvas; 91 | public function new(mini : MiniCanvas) 92 | this.mini = mini; 93 | 94 | function before() 95 | mini.clear(); 96 | 97 | function update(pos : Position) 98 | mini.dot(pos.x, pos.y, 2, 0x000000FF); 99 | } 100 | 101 | class UpdateMovement implements ISystem { 102 | function update(pos : Position, vel : Velocity) { 103 | var dx = pos.x + vel.vx, 104 | dy = pos.y + vel.vy; 105 | if(dx <= 0 && vel.vx < 0 || dx >= Game.width && vel.vx > 0) 106 | vel.vx = -vel.vx; 107 | else 108 | pos.x = dx; 109 | if(dy <= 0 && vel.vy < 0 || dy >= Game.height && vel.vy > 0) 110 | vel.vy = -vel.vy; 111 | else 112 | pos.y = dy; 113 | } 114 | } 115 | ``` 116 | 117 | ### ISystem 118 | 119 | Systems must implement ISystem. System classes do not have to implement the required `__process__` because this field is automatically built. 120 | 121 | A system must at least define a method `update()`. The method can take 0 or more arguments. If arguments are defined, they must be components; a component is an instance of any `Class`. 122 | 123 | The `update()` method will be invoked when the corresponding phase is updated. `update()` will be called only once if it takes no arguments, or once for every entity that matches the function requirements. An entity matches the `update` requirements if it has a matching component for each of the function arguments. 124 | 125 | Optionally a System can expose the following members: 126 | 127 | * `function after() : Void` 128 | 129 | Executes after each cycle of `update(/*...*/)`. It only makes sense when `update` takes at least one argument. 130 | 131 | * `function before() : Void` 132 | 133 | Executes before each cycle of `update(/*...*/)`. It only makes sense when `update` takes at least one argument. 134 | 135 | * `var engine : Engine` 136 | 137 | Gets a reference to engine. Useful to dynamically create more entities. 138 | 139 | * `var entity : Entity` 140 | 141 | To only be declared when `update(/*...*/)` takes at least one argument. `entity` will reference the container of the components that are currently processed by the `update` method. 142 | 143 | * `var timeDelta : Float` 144 | 145 | Brings a value (in millisecond) defining the time elapsed since the latest iteration. 146 | 147 | If the System exposes any of these members, they will be automatically populated at the right time. So no initialization is required or desired. Also they will be automatically changed to `public` if they are not already. 148 | 149 | Sometimes you want to be able to iterate over collections of entities that satisfy certain requirements. For example, it can be extremely useful for collisions. In that case you can define one (or more) fields of type `edge.View(T)`. Where `T` is the type of an anonymous object where each field must be have the type of a component. 150 | 151 | ```haxe 152 | var targets : View<{ position : Position, life : Life }>; 153 | var entity : Entity; 154 | 155 | function update(position : Position, bullet : Bullet) { 156 | for(item in targets) { 157 | var target = item.data; // item.data stores the components 158 | // hit the target? 159 | if(areNear(position, target.position)) { 160 | // assign damage 161 | life.hitPoints -= bullet.damage; 162 | 163 | // remove bullet 164 | entity.destroy(); 165 | 166 | // life is zero remove target 167 | if(life <= 0) 168 | item.entity.destroy(); // item.entity references the target entity 169 | } 170 | } 171 | } 172 | ``` 173 | 174 | System can also receive notifications when an entity has been added or removed from a `View`. 175 | 176 | From the example above, if you want to perform a special operation when a new target is paired with your system you can define the following method: 177 | 178 | ```haxe 179 | function targetsAdded(entity : Entity, data { position : Position, life : Life }) { 180 | // do something with the newly added entity/components 181 | } 182 | ``` 183 | 184 | The magic here is in the name of the method that needs to follow the format: 185 | 186 | ``` 187 | ${viewFieldName}Added 188 | ``` 189 | 190 | The same signature with `Removed` can be used to define a method that does some cleanup when an entity is unpaired from a view. 191 | 192 | A special `View` is created for the method `update` called `updateItems`. For that you can define either, both or neither of `updateAdded`/`updateRemoved` methods. 193 | 194 | For this `update` function: 195 | 196 | ```haxe 197 | function update(position : Position, bullet : Bullet) 198 | ``` 199 | 200 | The added/removed methods will look like: 201 | 202 | ```haxe 203 | function updateAdded(entity : Entity, data : { position : Position, bullet : Bullet }) {} 204 | 205 | function updateRemoved(entity : Entity, data : { position : Position, bullet : Bullet }) {} 206 | ``` 207 | 208 | ### IComponent 209 | 210 | Even if not required, your components can implement `IComponent`. Doing so your components will gain the following super-powers for free: 211 | 212 | * you don't need to setup a constructor, if it doesn't exist, one will be created for you and it will take the same arguments as the variable fields declared in the component. 213 | * all variables are automatically made public. 214 | * a method `toString` is also created to simplify the debugging of your code. 215 | -------------------------------------------------------------------------------- /src/edge/core/macro/BuildSystemProcess.hx: -------------------------------------------------------------------------------- 1 | package edge.core.macro; 2 | 3 | import haxe.macro.Context; 4 | import haxe.macro.Expr; 5 | import haxe.macro.Type; 6 | using haxe.macro.ComplexTypeTools; 7 | using haxe.macro.TypeTools; 8 | import edge.core.macro.Macros.*; 9 | 10 | class BuildSystemProcess { 11 | public static function createProcessType(systemName : String, processName : String, systemFields : Array) { 12 | var pack = processName.split('.'), 13 | name = pack.pop(), 14 | type = Context.getType(systemName), 15 | system = type.toComplexType(), 16 | classType = switch type { case TInst(cls, _): cls.get(); case _: null; }, 17 | fields = [], 18 | kind = TDClass( 19 | null, 20 | [{ pack : ['edge', 'core'], name : 'ISystemProcess' }], 21 | false 22 | ); 23 | 24 | injectConstructor(system, fields); 25 | injectRemoveEntity(fields); 26 | injectAddEntity(fields); 27 | injectSystemField(system, fields); 28 | injectUpdate(classType, systemFields, fields); 29 | injectViews(systemFields, fields); 30 | injectUpdateMatchRequirements(systemFields, fields); 31 | 32 | Context.defineType({ 33 | pos : Context.currentPos(), 34 | pack : pack, 35 | name : name, 36 | meta : [{ 37 | pos : Context.currentPos(), 38 | name : ":access", 39 | params : [macro edge.View] 40 | }], 41 | kind : kind, 42 | fields : fields 43 | }); 44 | } 45 | 46 | static function injectViews(systemFields : Array, fields : Array) 47 | for(field in collectViewFields(systemFields)) 48 | injectView(field, systemFields, fields); 49 | 50 | static function injectView(info : { name : String, types : Array, field : Field }, systemFields : Array, fields : Array) { 51 | var name = info.name; 52 | makeFieldPublic(info.field); 53 | appendExprToFieldFunction( 54 | findField(fields, "new"), 55 | Context.parse('system.$name = new edge.View()', Context.currentPos()) 56 | ); 57 | 58 | injectViewMatchRequirements(info, systemFields, fields); 59 | } 60 | 61 | static function injectViewMatchRequirements(info : { name : String, types : Array, field : Field }, systemFields : Array, fields : Array) { 62 | var name = info.name, 63 | types = info.types, 64 | sexprs = []; 65 | 66 | sexprs.push('var removed = system.$name.tryRemove(entity)'); 67 | sexprs.push('var count = ' + types.length); 68 | sexprs.push('var o : {' + 69 | types.map(function(type) { 70 | return type.name + " : " + switch type.kind { 71 | case FVar(t, _): Context.follow(t.toType()).toComplexType().toString(); 72 | case _: ""; 73 | }; 74 | }).join(", ") + 75 | '} = {' + 76 | types.map(function(type) return '${type.name} : null').join(", ") + '}'); 77 | var expr = 'for(component in entity.components()) {\n'; 78 | for(type in types) { 79 | var t = switch type.kind { 80 | case FVar(t, _): Context.follow(t.toType()).toComplexType().toString(); 81 | case _: ""; 82 | }; 83 | expr += ' if(Std.is(component, $t)) {\n'; 84 | expr += ' o.${type.name} = cast component;\n'; 85 | expr += ' if(--count == 0) break; else continue;\n'; 86 | expr += ' }\n'; 87 | } 88 | expr += '}'; 89 | sexprs.push(expr); 90 | sexprs.push('var added = count == 0 && system.$name.tryAdd(entity, o)'); 91 | 92 | if(hasFunField(systemFields, '${name}Removed')) { 93 | sexprs.push('if((null != removed) && !added) system.${name}Removed(entity, removed)'); 94 | } 95 | if(hasFunField(systemFields, '${name}Added')) { 96 | sexprs.push('if(added && (null == removed)) system.${name}Added(entity, o)'); 97 | } 98 | 99 | var exprs = sexprs.map(function(sexpr) return Context.parse(sexpr, Context.currentPos())), 100 | methodName = '${name}MatchRequirements'; 101 | fields.push(createFunctionField( 102 | methodName, 103 | [{ name : "entity", type : macro : edge.Entity }], 104 | macro $b{exprs} 105 | )); 106 | 107 | appendExprToFieldFunction( 108 | findField(fields, "addEntity"), 109 | Context.parse('$methodName(entity)', Context.currentPos()) 110 | ); 111 | 112 | expr = hasFunField(systemFields, '${name}Removed') ? 113 | '{ var removed = system.$name.tryRemove(entity); if(removed != null) system.${name}Removed(entity, removed); }' : 114 | 'system.$name.tryRemove(entity)'; 115 | appendExprToFieldFunction( 116 | findField(fields, "removeEntity"), 117 | Context.parse(expr, Context.currentPos()) 118 | ); 119 | } 120 | 121 | static function injectUpdate(systemType : ClassType, systemFields : Array, fields : Array) { 122 | var exprs = []; 123 | if(hasVarField(systemFields, "engine")) 124 | exprs.push(macro system.engine = engine); 125 | if(hasVarField(systemFields, "timeDelta")) 126 | exprs.push(macro system.timeDelta = delta); 127 | 128 | var update = findField(systemFields, "update"), 129 | constructor = findField(fields, "new"); 130 | 131 | exprs.push(macro var result = true); 132 | if(fieldFunctionHasArguments(update)) { 133 | var args = fieldFunctionArguments(update), 134 | fieldTypes = args.map(function(arg) : Field { 135 | var t = Context.follow(arg.type.toType()).toComplexType(), 136 | kind : FieldType = FVar(t, null); 137 | return { 138 | pos : Context.currentPos(), 139 | name : arg.name, 140 | kind : kind 141 | }; 142 | }), 143 | type = TPath({ 144 | pack : ["edge"], 145 | name : "View", 146 | params : [TPType(TAnonymous(fieldTypes))] 147 | }); 148 | fields.push(createVarField("updateItems", type)); 149 | 150 | var expr = hasFunField(systemFields, 'updateRemoved') ? 151 | '{ var removed = updateItems.tryRemove(entity); if(removed != null) system.updateRemoved(entity, removed); }' : 152 | 'updateItems.tryRemove(entity)'; 153 | appendExprToFieldFunction( 154 | findField(fields, "removeEntity"), 155 | Context.parse(expr, Context.currentPos()) 156 | ); 157 | 158 | appendExprToFieldFunction( 159 | constructor, 160 | macro updateItems = new edge.View() 161 | ); 162 | 163 | if(hasFunField(systemFields, "before") || isFieldInHirearchy(systemType, "before")) 164 | exprs.push(macro if(updateItems.count > 0) system.before()); 165 | // create loop expression 166 | exprs.push(macro var data); 167 | var expr = '\nfor(item in updateItems) {\n'; 168 | // set entity if required 169 | if(hasVarField(systemFields, "entity")) 170 | expr += ' system.entity = item.entity;\n'; 171 | // call update 172 | expr += ' data = item.data;\n'; 173 | expr += ' result = system.update(' + args.map(function(arg) { 174 | return 'data.${arg.name}'; 175 | }).join(", ") + ');\n'; 176 | expr += ' if(!result) break;'; 177 | expr += '}'; 178 | exprs.push(Context.parse(expr, Context.currentPos())); 179 | } else { 180 | if(hasFunField(systemFields, "before") || isFieldInHirearchy(systemType, "before")) 181 | exprs.push(macro system.before()); 182 | exprs.push(macro result = system.update()); 183 | } 184 | if(hasFunField(systemFields, "after") || isFieldInHirearchy(systemType, "after")) 185 | exprs.push(macro system.after()); 186 | exprs.push(macro return result); 187 | 188 | //trace(haxe.macro.ExprTools.toString(macro $b{exprs})); 189 | 190 | fields.push(createFunctionField( 191 | "update", 192 | [{ name : "engine", type : macro : edge.Engine }, 193 | { name : "delta", type : macro : Float }], 194 | macro : Bool, 195 | macro $b{exprs} 196 | )); 197 | } 198 | 199 | static function injectUpdateMatchRequirements(systemFields : Array, fields : Array) { 200 | var args = fieldFunctionArguments(findField(systemFields, "update")); 201 | if(args.length == 0) return; 202 | 203 | var sexprs = []; 204 | sexprs.push('var removed = updateItems.tryRemove(entity)'); 205 | sexprs.push('var count = ' + args.length); 206 | sexprs.push('var o : {' + args.map(function(arg) return '${arg.name} : ${Context.follow(arg.type.toType()).toComplexType().toString()}').join(", ") + '} = {' + args.map(function(arg) return '${arg.name} : null').join(", ") + '}'); 207 | 208 | var expr = 'for(component in entity.components()) {\n'; 209 | for(arg in args) { 210 | var t = Context.follow(arg.type.toType()).toComplexType().toString(); 211 | expr += ' if(Std.is(component, $t)) {\n'; 212 | expr += ' o.${arg.name} = cast component;\n'; 213 | expr += ' if(--count == 0) break; else continue;\n'; 214 | expr += ' }\n'; 215 | } 216 | expr += '}'; 217 | sexprs.push(expr); 218 | 219 | sexprs.push('var added = count == 0 && updateItems.tryAdd(entity, o)'); 220 | 221 | if(hasFunField(systemFields, 'updateRemoved')) { 222 | sexprs.push('if((null != removed) && !added) system.updateRemoved(entity, removed)'); 223 | } 224 | if(hasFunField(systemFields, 'updateAdded')) { 225 | sexprs.push('if(added && (null == removed)) system.updateAdded(entity, o)'); 226 | } 227 | 228 | var exprs = sexprs.map(function(sexpr) return Context.parse(sexpr, Context.currentPos())); 229 | fields.push(createFunctionField( 230 | "updateMatchRequirements", 231 | [{ name : "entity", type : macro : edge.Entity }], 232 | macro $b{exprs} 233 | )); 234 | 235 | appendExprToFieldFunction( 236 | findField(fields, "addEntity"), 237 | macro updateMatchRequirements(entity)); 238 | } 239 | 240 | static function injectRemoveEntity(fields : Array) 241 | fields.push(createFunctionField( 242 | "removeEntity", 243 | [{ name : "entity", type : macro : edge.Entity }] 244 | )); 245 | 246 | static function injectAddEntity(fields : Array) 247 | fields.push(createFunctionField( 248 | "addEntity", 249 | [{ name : "entity", type : macro : edge.Entity }] 250 | )); 251 | 252 | static function injectSystemField(system : ComplexType, fields : Array) 253 | fields.push(createVarField("system", system)); 254 | 255 | static function injectConstructor(system : ComplexType, fields : Array) 256 | fields.push(createFunctionField( 257 | "new", 258 | [{ name : "system", type : system }], 259 | macro this.system = system 260 | )); 261 | 262 | static function collectViewFields(fields : Array) : Array<{ name : String, types : Array, field : Field }> { 263 | var results = []; 264 | for(field in fields) { 265 | switch field.kind { 266 | case FVar(tp, _): 267 | if(tp == null) continue; 268 | tp = Context.follow(tp.toType()).toComplexType(); 269 | switch tp { 270 | case TPath({ name : "View", pack : ["edge"], params : [TPType(TAnonymous(p))] }): 271 | results.push({ 272 | name : field.name, 273 | types : p, 274 | field : field 275 | }); 276 | case _: 277 | }; 278 | case _: 279 | } 280 | } 281 | return results; 282 | } 283 | } 284 | -------------------------------------------------------------------------------- /test/TestAll.hx: -------------------------------------------------------------------------------- 1 | using utest.Assert; 2 | import utest.Runner; 3 | import utest.ui.Report; 4 | using thx.Iterators; 5 | 6 | import edge.*; 7 | 8 | class TestAll { 9 | 10 | public function testUpdateRemoved() { 11 | var engine = new Engine(), 12 | phase = engine.createPhase(), 13 | system = new UpdateRemovedSystem(); 14 | phase.add(system); 15 | Assert.same([], system.results); 16 | engine.create([new B()]); // doesn't affect the system 17 | Assert.same([], system.results); 18 | var e = engine.create([new A()]); 19 | Assert.same([], system.results); 20 | phase.update(0); 21 | Assert.same([2], system.results); 22 | e.removeType(A); 23 | Assert.same([2, 1], system.results); 24 | e.add(new A()); 25 | e.removeType(A); 26 | Assert.same([2, 1, 1], system.results); 27 | } 28 | 29 | public function testUpdateAdded() { 30 | var engine = new Engine(), 31 | phase = engine.createPhase(), 32 | system = new UpdateAddedSystem(); 33 | phase.add(system); 34 | Assert.same([], system.results); 35 | engine.create([new B()]); // doesn't affect the system 36 | Assert.same([], system.results); 37 | var e = engine.create([new A()]); 38 | Assert.same([1], system.results); 39 | phase.update(0); 40 | Assert.same([1, 2], system.results); 41 | e.removeType(A); 42 | Assert.same([1, 2], system.results); 43 | e.add(new A()); 44 | Assert.same([1, 2, 1], system.results); 45 | } 46 | 47 | public function testBefore() { 48 | var engine = new Engine(), 49 | phase = engine.createPhase(), 50 | system = new BeforeSystem(); 51 | phase.add(system); 52 | phase.update(0); 53 | Assert.same([], system.results); 54 | engine.create([new A()]); 55 | phase.update(0); 56 | Assert.same([1,2], system.results); 57 | } 58 | 59 | public function testMultipleViews() { 60 | var engine = new Engine(), 61 | phase = engine.createPhase(), 62 | system = new HasAandBSystem(); 63 | 64 | Assert.equals(0, system.viewA.count); 65 | Assert.equals(0, system.viewB.count); 66 | phase.add(system); 67 | Assert.equals(0, system.viewA.count); 68 | Assert.equals(0, system.viewB.count); 69 | 70 | var e = engine.create([new A()]); 71 | Assert.equals(1, system.viewA.count); 72 | Assert.equals(0, system.viewB.count); 73 | 74 | e.add(new B()); 75 | Assert.equals(1, system.viewA.count); 76 | Assert.equals(1, system.viewB.count); 77 | 78 | engine.create([new B()]); 79 | Assert.equals(1, system.viewA.count); 80 | Assert.equals(2, system.viewB.count); 81 | } 82 | 83 | public function testPhaseNodes() { 84 | var engine = new Engine(), 85 | phase = engine.createPhase(), 86 | it = phase.systems(); 87 | Assert.isFalse(it.hasNext()); 88 | phase.add(new Components2System()); 89 | it = phase.systems(); 90 | Assert.isTrue(it.hasNext()); 91 | Assert.notNull(it.next()); 92 | Assert.isFalse(it.hasNext()); 93 | phase.add(new Components1System()); 94 | it = phase.systems(); 95 | Assert.isTrue(it.hasNext()); 96 | Assert.is(it.next(), Components2System); 97 | Assert.is(it.next(), Components1System); 98 | Assert.isFalse(it.hasNext()); 99 | phase.removeType(Components2System); 100 | it = phase.systems(); 101 | Assert.isTrue(it.hasNext()); 102 | Assert.is(it.next(), Components1System); 103 | Assert.isFalse(it.hasNext()); 104 | } 105 | 106 | public function testEngineComponents2System() { 107 | var engine = new Engine(), 108 | phase = engine.createPhase(), 109 | system = new Components2System(); 110 | phase.add(system); 111 | Assert.equals(0, system.count); 112 | phase.update(0); 113 | Assert.equals(0, system.count); 114 | var entity = engine.create([new A(), new B()]); 115 | Assert.equals(engine, entity.engine); 116 | phase.update(0); 117 | Assert.equals(1, system.count); 118 | entity.destroy(); 119 | Assert.isNull(entity.engine); 120 | phase.update(0); 121 | Assert.equals(1, system.count); 122 | entity = engine.create([new A(), new B()]); 123 | phase.update(0); 124 | Assert.equals(2, system.count); 125 | entity.removeType(A); 126 | phase.update(0); 127 | Assert.equals(2, system.count); 128 | } 129 | 130 | public function testEngineComponents1System() { 131 | var engine = new Engine(), 132 | phase = engine.createPhase(), 133 | system = new Components1System(); 134 | phase.add(system); 135 | Assert.equals(0, system.count); 136 | phase.update(0); 137 | Assert.equals(0, system.count); 138 | var entity = engine.create([new B()]); 139 | phase.update(0); 140 | Assert.equals(1, system.count); 141 | entity.destroy(); 142 | phase.update(0); 143 | Assert.equals(1, system.count); 144 | entity = engine.create([new B()]); 145 | phase.update(0); 146 | Assert.equals(2, system.count); 147 | entity.removeType(B); 148 | phase.update(0); 149 | Assert.equals(2, system.count); 150 | } 151 | 152 | public function testEngineComponents1MissingSystem() { 153 | var engine = new Engine(), 154 | phase = engine.createPhase(), 155 | system = new Components1System(); 156 | phase.add(system); 157 | Assert.equals(0, system.count); 158 | phase.update(0); 159 | Assert.equals(0, system.count); 160 | var entity = engine.create([new A()]); 161 | phase.update(0); 162 | Assert.equals(0, system.count); 163 | entity.destroy(); 164 | phase.update(0); 165 | Assert.equals(0, system.count); 166 | } 167 | 168 | public function testEngineNoComponentSystem() { 169 | var engine = new Engine(), 170 | phase = engine.createPhase(), 171 | system = new NoComponentsSystem(); 172 | phase.add(system); 173 | Assert.equals(0, system.count); 174 | phase.update(0); 175 | Assert.equals(1, system.count); 176 | phase.update(0); 177 | Assert.equals(2, system.count); 178 | phase.remove(system); 179 | phase.update(0); 180 | Assert.equals(2, system.count); 181 | } 182 | 183 | public function testEngineSystemCounting() { 184 | var engine = new Engine(), 185 | phase = engine.createPhase(), 186 | s1 = new NoComponentsSystem(), 187 | s2 = new Components2System(); 188 | assertNumberOfEntities(engine, 0); 189 | assertNumberOfSystems(engine, 0); 190 | phase.add(s1); 191 | assertNumberOfSystems(engine, 1); 192 | phase.add(s2); 193 | assertNumberOfSystems(engine, 2); 194 | phase.remove(s1); 195 | assertNumberOfSystems(engine, 1); 196 | phase.remove(s1); 197 | assertNumberOfSystems(engine, 1); 198 | phase.remove(s2); 199 | assertNumberOfSystems(engine, 0); 200 | } 201 | 202 | public function testEngineEntity() { 203 | var engine = new Engine(); 204 | assertNumberOfEntities(engine, 0); 205 | assertNumberOfSystems(engine, 0); 206 | var e1 = engine.create(); 207 | assertNumberOfEntities(engine, 1); 208 | assertNumberOfSystems(engine, 0); 209 | var e2 = engine.create(); 210 | assertNumberOfEntities(engine, 2); 211 | e1.destroy(); 212 | assertNumberOfEntities(engine, 1); 213 | e1.destroy(); 214 | assertNumberOfEntities(engine, 1); 215 | e2.destroy(); 216 | assertNumberOfEntities(engine, 0); 217 | } 218 | 219 | public function testEntity() { 220 | var engine = new Engine(), 221 | entity = engine.create(); 222 | entity.add(new A()); 223 | assertNumberOfComponents(entity, 1); 224 | entity.add(new B()); 225 | assertNumberOfComponents(entity, 2); 226 | var a = new A(); 227 | entity.add(a); 228 | assertNumberOfComponents(entity, 2); 229 | entity.remove(a); 230 | assertNumberOfComponents(entity, 1); 231 | entity.removeType(B); 232 | assertNumberOfComponents(entity, 0); 233 | } 234 | 235 | public function testUpdateReturn() { 236 | var engine = new Engine(), 237 | phase = engine.createPhase(), 238 | system = new ReturnSystem(); 239 | phase.add(system); 240 | Assert.equals(0, system.count); 241 | } 242 | 243 | public function testInheritedConstructor() { 244 | for(field in haxe.rtti.Rtti.getRtti(E).fields) { 245 | if (field.name == "new") { 246 | switch (field.type) { 247 | case CFunction(args, returnType): 248 | Assert.equals(args.length, 3); 249 | default: 250 | } 251 | } 252 | } 253 | } 254 | 255 | public function testInheritedToString() 256 | Assert.equals((new E(1, 2, 3)).toString(), "E(foo=$foo,bar=$bar,foobar=$foobar)"); 257 | 258 | public function assertNumberOfComponents(entity : Entity, qt : Int, ?pos : haxe.PosInfos) 259 | Assert.equals(qt, entity.components().toArray().length, pos); 260 | 261 | public function assertNumberOfEntities(engine : Engine, qt : Int, ?pos : haxe.PosInfos) 262 | Assert.equals(qt, engine.entities().toArray().length, pos); 263 | 264 | public function assertNumberOfSystems(engine : Engine, qt : Int, ?pos : haxe.PosInfos) { 265 | var count = 0; 266 | engine.eachSystem(function(_) count++); 267 | Assert.equals(qt, count, pos); 268 | } 269 | 270 | public static function main() { 271 | var runner = new Runner(); 272 | 273 | runner.addCase(new TestAll()); 274 | 275 | Report.create(runner); 276 | runner.run(); 277 | } 278 | 279 | public function new() {} 280 | } 281 | 282 | class NoComponentsSystem implements ISystem { 283 | public var count(default, null) = 0; 284 | public function update() { 285 | count++; 286 | } 287 | } 288 | 289 | class Components2System implements ISystem { 290 | public var count(default, null) = 0; 291 | public function update(b : B, a : A) { 292 | Assert.is(b, B); 293 | Assert.is(a, A); 294 | count++; 295 | } 296 | } 297 | 298 | class Components1System implements ISystem { 299 | public var count(default, null) = 0; 300 | public var entity : Entity; 301 | var engine : Engine; 302 | public function update(b : B) { 303 | Assert.is(b, B); 304 | count++; 305 | } 306 | } 307 | 308 | class ComponentsEntitiesSystem implements ISystem { 309 | public var count(default, null) = 0; 310 | public var entities : View<{ a : A }>; 311 | public function update(b : B) { 312 | Assert.is(b, B); 313 | count++; 314 | } 315 | } 316 | 317 | class HasAandBSystem implements ISystem { 318 | public var viewA : View<{ a : A }>; 319 | public var viewB : View<{ b : B }>; 320 | 321 | public function update() { 322 | 323 | } 324 | } 325 | 326 | class BeforeSystem implements ISystem { 327 | public var results : Array = []; 328 | 329 | public function before() { 330 | results.push(1); 331 | } 332 | 333 | public function update(a : A) { 334 | results.push(2); 335 | } 336 | } 337 | 338 | class UpdateAddedSystem implements ISystem { 339 | public var results : Array = []; 340 | 341 | public function updateAdded(entity : Entity, o : { a : A }) { 342 | Assert.is(entity, Entity); 343 | results.push(1); 344 | } 345 | 346 | public function update(a : A) { 347 | results.push(2); 348 | } 349 | } 350 | 351 | class UpdateRemovedSystem implements ISystem { 352 | public var results : Array = []; 353 | 354 | public function updateRemoved(entity : Entity, _) { 355 | Assert.is(entity, Entity); 356 | results.push(1); 357 | } 358 | 359 | public function update(a : A) { 360 | results.push(2); 361 | } 362 | } 363 | 364 | class UpdateAddedRemovedSystem implements ISystem { 365 | public var results : Array = []; 366 | 367 | public function updateAdded(entity : Entity, o : { a : A }) { 368 | Assert.is(entity, Entity); 369 | results.push(1); 370 | } 371 | 372 | public function updateRemoved(entity : Entity, _) { 373 | Assert.is(entity, Entity); 374 | results.push(2); 375 | } 376 | 377 | public function update(a : A) { 378 | results.push(3); 379 | } 380 | } 381 | 382 | class A { 383 | public function new(){} 384 | } 385 | 386 | class B { 387 | public function new(){} 388 | } 389 | 390 | class C implements IComponent { 391 | public var foo:Int; 392 | } 393 | 394 | class D extends C { 395 | public var bar:Int; 396 | } 397 | 398 | @:rtti 399 | class E extends D { 400 | public var foobar:Int; 401 | } 402 | 403 | class ReturnSystem implements ISystem { 404 | public var count(default, null) = 0; 405 | public var entity : Entity; 406 | var engine : Engine; 407 | public function update() { 408 | return; 409 | count++; 410 | } 411 | } 412 | -------------------------------------------------------------------------------- /demo/basic/bin/main.js: -------------------------------------------------------------------------------- 1 | (function (console) { "use strict"; 2 | function $extend(from, fields) { 3 | function Inherit() {} Inherit.prototype = from; var proto = new Inherit(); 4 | for (var name in fields) proto[name] = fields[name]; 5 | if( fields.toString !== Object.prototype.toString ) proto.toString = fields.toString; 6 | return proto; 7 | } 8 | var EReg = function(r,opt) { 9 | opt = opt.split("u").join(""); 10 | this.r = new RegExp(r,opt); 11 | }; 12 | EReg.__name__ = ["EReg"]; 13 | EReg.prototype = { 14 | match: function(s) { 15 | if(this.r.global) this.r.lastIndex = 0; 16 | this.r.m = this.r.exec(s); 17 | this.r.s = s; 18 | return this.r.m != null; 19 | } 20 | ,matched: function(n) { 21 | if(this.r.m != null && n >= 0 && n < this.r.m.length) return this.r.m[n]; else throw "EReg::matched"; 22 | } 23 | ,replace: function(s,by) { 24 | return s.replace(this.r,by); 25 | } 26 | ,__class__: EReg 27 | }; 28 | var Game = function() { }; 29 | Game.__name__ = ["Game"]; 30 | Game.main = function() { 31 | var mini = minicanvas.MiniCanvas.create(Game.width,Game.height).display("basic example"); 32 | var world = new edge.World(); 33 | var _g = 0; 34 | while(_g < 300) { 35 | var i = _g++; 36 | world.engine.create([new Position(Math.random() * Game.width,Math.random() * Game.height),new Velocity(Math.random() * 2 - 1,Math.random() * 2 - 1)]); 37 | } 38 | var _g1 = 0; 39 | while(_g1 < 20) { 40 | var i1 = _g1++; 41 | world.engine.create([new Position(Math.random() * Game.width,Math.random() * Game.height)]); 42 | } 43 | world.physics.add(new UpdateMovement()); 44 | world.render.add(new RenderDots(mini)); 45 | world.start(); 46 | }; 47 | var edge = {}; 48 | edge.IComponent = function() { }; 49 | edge.IComponent.__name__ = ["edge","IComponent"]; 50 | var Position = function(x,y) { 51 | this.x = x; 52 | this.y = y; 53 | }; 54 | Position.__name__ = ["Position"]; 55 | Position.__interfaces__ = [edge.IComponent]; 56 | Position.prototype = { 57 | __class__: Position 58 | }; 59 | var Velocity = function(vx,vy) { 60 | this.vx = vx; 61 | this.vy = vy; 62 | }; 63 | Velocity.__name__ = ["Velocity"]; 64 | Velocity.__interfaces__ = [edge.IComponent]; 65 | Velocity.prototype = { 66 | __class__: Velocity 67 | }; 68 | edge.ISystem = function() { }; 69 | edge.ISystem.__name__ = ["edge","ISystem"]; 70 | edge.ISystem.prototype = { 71 | __class__: edge.ISystem 72 | }; 73 | var RenderDots = function(mini) { 74 | this.mini = mini; 75 | this.__process__ = new RenderDots_SystemProcess(this); 76 | }; 77 | RenderDots.__name__ = ["RenderDots"]; 78 | RenderDots.__interfaces__ = [edge.ISystem]; 79 | RenderDots.prototype = { 80 | before: function() { 81 | this.mini.clear(); 82 | } 83 | ,update: function(pos) { 84 | this.mini.dot(pos.x,pos.y,2,255); 85 | } 86 | ,__class__: RenderDots 87 | }; 88 | var UpdateMovement = function() { 89 | this.__process__ = new UpdateMovement_SystemProcess(this); 90 | }; 91 | UpdateMovement.__name__ = ["UpdateMovement"]; 92 | UpdateMovement.__interfaces__ = [edge.ISystem]; 93 | UpdateMovement.prototype = { 94 | update: function(pos,vel) { 95 | var dx = pos.x + vel.vx; 96 | var dy = pos.y + vel.vy; 97 | if(dx <= 0 && vel.vx < 0 || dx >= Game.width && vel.vx > 0) vel.vx = -vel.vx; else pos.x = dx; 98 | if(dy <= 0 && vel.vy < 0 || dy >= Game.height && vel.vy > 0) vel.vy = -vel.vy; else pos.y = dy; 99 | } 100 | ,__class__: UpdateMovement 101 | }; 102 | var HxOverrides = function() { }; 103 | HxOverrides.__name__ = ["HxOverrides"]; 104 | HxOverrides.cca = function(s,index) { 105 | var x = s.charCodeAt(index); 106 | if(x != x) return undefined; 107 | return x; 108 | }; 109 | HxOverrides.substr = function(s,pos,len) { 110 | if(pos != null && pos != 0 && len != null && len < 0) return ""; 111 | if(len == null) len = s.length; 112 | if(pos < 0) { 113 | pos = s.length + pos; 114 | if(pos < 0) pos = 0; 115 | } else if(len < 0) len = s.length + len - pos; 116 | return s.substr(pos,len); 117 | }; 118 | HxOverrides.iter = function(a) { 119 | return { cur : 0, arr : a, hasNext : function() { 120 | return this.cur < this.arr.length; 121 | }, next : function() { 122 | return this.arr[this.cur++]; 123 | }}; 124 | }; 125 | Math.__name__ = ["Math"]; 126 | edge.core = {}; 127 | edge.core.ISystemProcess = function() { }; 128 | edge.core.ISystemProcess.__name__ = ["edge","core","ISystemProcess"]; 129 | edge.core.ISystemProcess.prototype = { 130 | __class__: edge.core.ISystemProcess 131 | }; 132 | var RenderDots_SystemProcess = function(system) { 133 | this.system = system; 134 | this.updateItems = new edge.View(); 135 | }; 136 | RenderDots_SystemProcess.__name__ = ["RenderDots_SystemProcess"]; 137 | RenderDots_SystemProcess.__interfaces__ = [edge.core.ISystemProcess]; 138 | RenderDots_SystemProcess.prototype = { 139 | removeEntity: function(entity) { 140 | this.updateItems.tryRemove(entity); 141 | } 142 | ,addEntity: function(entity) { 143 | this.updateMatchRequirements(entity); 144 | } 145 | ,update: function(engine,delta) { 146 | if(this.updateItems.count > 0) this.system.before(); 147 | var data; 148 | var $it0 = this.updateItems.iterator(); 149 | while( $it0.hasNext() ) { 150 | var item = $it0.next(); 151 | data = item.data; 152 | this.system.update(data.pos); 153 | } 154 | } 155 | ,updateMatchRequirements: function(entity) { 156 | var removed = this.updateItems.tryRemove(entity); 157 | var count = 1; 158 | var o = { pos : null}; 159 | var $it0 = entity.map.iterator(); 160 | while( $it0.hasNext() ) { 161 | var component = $it0.next(); 162 | if(js.Boot.__instanceof(component,Position)) { 163 | o.pos = component; 164 | if(--count == 0) break; else continue; 165 | } 166 | } 167 | var added = count == 0 && this.updateItems.tryAdd(entity,o); 168 | } 169 | ,__class__: RenderDots_SystemProcess 170 | }; 171 | var Std = function() { }; 172 | Std.__name__ = ["Std"]; 173 | Std.string = function(s) { 174 | return js.Boot.__string_rec(s,""); 175 | }; 176 | Std.parseInt = function(x) { 177 | var v = parseInt(x,10); 178 | if(v == 0 && (HxOverrides.cca(x,1) == 120 || HxOverrides.cca(x,1) == 88)) v = parseInt(x); 179 | if(isNaN(v)) return null; 180 | return v; 181 | }; 182 | var StringTools = function() { }; 183 | StringTools.__name__ = ["StringTools"]; 184 | StringTools.replace = function(s,sub,by) { 185 | return s.split(sub).join(by); 186 | }; 187 | var Type = function() { }; 188 | Type.__name__ = ["Type"]; 189 | Type.getClassName = function(c) { 190 | var a = c.__name__; 191 | if(a == null) return null; 192 | return a.join("."); 193 | }; 194 | var UpdateMovement_SystemProcess = function(system) { 195 | this.system = system; 196 | this.updateItems = new edge.View(); 197 | }; 198 | UpdateMovement_SystemProcess.__name__ = ["UpdateMovement_SystemProcess"]; 199 | UpdateMovement_SystemProcess.__interfaces__ = [edge.core.ISystemProcess]; 200 | UpdateMovement_SystemProcess.prototype = { 201 | removeEntity: function(entity) { 202 | this.updateItems.tryRemove(entity); 203 | } 204 | ,addEntity: function(entity) { 205 | this.updateMatchRequirements(entity); 206 | } 207 | ,update: function(engine,delta) { 208 | var data; 209 | var $it0 = this.updateItems.iterator(); 210 | while( $it0.hasNext() ) { 211 | var item = $it0.next(); 212 | data = item.data; 213 | this.system.update(data.pos,data.vel); 214 | } 215 | } 216 | ,updateMatchRequirements: function(entity) { 217 | var removed = this.updateItems.tryRemove(entity); 218 | var count = 2; 219 | var o = { pos : null, vel : null}; 220 | var $it0 = entity.map.iterator(); 221 | while( $it0.hasNext() ) { 222 | var component = $it0.next(); 223 | if(js.Boot.__instanceof(component,Position)) { 224 | o.pos = component; 225 | if(--count == 0) break; else continue; 226 | } 227 | if(js.Boot.__instanceof(component,Velocity)) { 228 | o.vel = component; 229 | if(--count == 0) break; else continue; 230 | } 231 | } 232 | var added = count == 0 && this.updateItems.tryAdd(entity,o); 233 | } 234 | ,__class__: UpdateMovement_SystemProcess 235 | }; 236 | edge.Engine = function() { 237 | this.mapEntities = new haxe.ds.ObjectMap(); 238 | this.listPhases = []; 239 | }; 240 | edge.Engine.__name__ = ["edge","Engine"]; 241 | edge.Engine.prototype = { 242 | create: function(components) { 243 | var entity = new edge.Entity(this,components); 244 | this.mapEntities.set(entity,true); 245 | this.matchSystems(entity); 246 | return entity; 247 | } 248 | ,createPhase: function() { 249 | var phase = new edge.Phase(this); 250 | this.listPhases.push(phase); 251 | return phase; 252 | } 253 | ,eachSystem: function(f) { 254 | var _g = 0; 255 | var _g1 = this.listPhases; 256 | while(_g < _g1.length) { 257 | var phase = _g1[_g]; 258 | ++_g; 259 | var $it0 = phase.systems(); 260 | while( $it0.hasNext() ) { 261 | var system = $it0.next(); 262 | f(system); 263 | } 264 | } 265 | } 266 | ,addSystem: function(phase,system) { 267 | this.eachSystem(function(s) { 268 | if(s == system) throw "System \"" + Std.string(system) + "\" already exists in Engine"; 269 | }); 270 | var $it0 = this.mapEntities.keys(); 271 | while( $it0.hasNext() ) { 272 | var entity = $it0.next(); 273 | system.__process__.addEntity(entity); 274 | } 275 | } 276 | ,removeSystem: function(system) { 277 | var $it0 = this.mapEntities.keys(); 278 | while( $it0.hasNext() ) { 279 | var entity = $it0.next(); 280 | system.__process__.removeEntity(entity); 281 | } 282 | } 283 | ,updateSystem: function(system,t) { 284 | system.__process__.update(this,t); 285 | } 286 | ,matchSystems: function(entity) { 287 | var _g = this; 288 | this.eachSystem(function(system) { 289 | system.__process__.addEntity(entity); 290 | }); 291 | } 292 | ,__class__: edge.Engine 293 | }; 294 | edge.Entity = function(engine,components) { 295 | this.engine = engine; 296 | this.map = new haxe.ds.StringMap(); 297 | if(null != components) this.addMany(components); 298 | }; 299 | edge.Entity.__name__ = ["edge","Entity"]; 300 | edge.Entity.prototype = { 301 | addMany: function(components) { 302 | var _g = this; 303 | if(null == this.engine) return; 304 | components.map(function(_) { 305 | _g._add(_); 306 | return; 307 | }); 308 | this.engine.matchSystems(this); 309 | } 310 | ,remove: function(component) { 311 | this._remove(component); 312 | this.engine.matchSystems(this); 313 | } 314 | ,_add: function(component) { 315 | var type = Type.getClassName(component == null?null:js.Boot.getClass(component)); 316 | if(this.map.exists(type)) this.remove(this.map.get(type)); 317 | this.map.set(type,component); 318 | } 319 | ,_remove: function(component) { 320 | var type = Type.getClassName(component == null?null:js.Boot.getClass(component)); 321 | this._removeTypeName(type); 322 | } 323 | ,_removeTypeName: function(type) { 324 | this.map.remove(type); 325 | } 326 | ,__class__: edge.Entity 327 | }; 328 | edge.Phase = function(engine) { 329 | this.engine = engine; 330 | this.mapSystem = new haxe.ds.ObjectMap(); 331 | this.mapType = new haxe.ds.StringMap(); 332 | }; 333 | edge.Phase.__name__ = ["edge","Phase"]; 334 | edge.Phase.prototype = { 335 | add: function(system) { 336 | this.remove(system); 337 | var node = this.createNode(system); 338 | if(null == this.first) { 339 | this.first = node; 340 | this.last = node; 341 | } else { 342 | node.prev = this.last; 343 | this.last.next = node; 344 | this.last = node; 345 | } 346 | } 347 | ,remove: function(system) { 348 | var node = this.mapSystem.h[system.__id__]; 349 | var key = this.key(system); 350 | this.mapType.remove(key); 351 | if(null == node) return; 352 | if(null != this.engine) this.engine.removeSystem(system); 353 | this.mapSystem.remove(system); 354 | if(node == this.first && node == this.last) this.first = this.last = null; else if(node == this.first) { 355 | this.first = node.next; 356 | node.next.prev = null; 357 | } else if(node == this.last) { 358 | this.first = node.prev; 359 | node.prev.next = null; 360 | } else { 361 | node.prev.next = node.next; 362 | node.next.prev = node.prev; 363 | } 364 | } 365 | ,systems: function() { 366 | return new edge.core.NodeSystemIterator(this.first); 367 | } 368 | ,update: function(t) { 369 | if(null == this.engine) return; 370 | var $it0 = this.systems(); 371 | while( $it0.hasNext() ) { 372 | var system = $it0.next(); 373 | this.engine.updateSystem(system,t); 374 | } 375 | } 376 | ,createNode: function(system) { 377 | var node = new edge.core.NodeSystem(system); 378 | this.mapSystem.set(system,node); 379 | var key = this.key(system); 380 | this.mapType.set(key,system); 381 | if(null != this.engine) this.engine.addSystem(this,system); 382 | return node; 383 | } 384 | ,key: function(system) { 385 | return Type.getClassName(system == null?null:js.Boot.getClass(system)); 386 | } 387 | ,__class__: edge.Phase 388 | }; 389 | edge.View = function() { 390 | this.map = new haxe.ds.ObjectMap(); 391 | this.count = 0; 392 | }; 393 | edge.View.__name__ = ["edge","View"]; 394 | edge.View.prototype = { 395 | iterator: function() { 396 | var _g = this; 397 | var keys = this.map.keys(); 398 | var holder = { entity : null, data : null}; 399 | return { hasNext : function() { 400 | return keys.hasNext(); 401 | }, next : function() { 402 | var key = keys.next(); 403 | holder.entity = key; 404 | holder.data = _g.map.h[key.__id__]; 405 | return holder; 406 | }}; 407 | } 408 | ,tryAdd: function(entity,data) { 409 | if(this.map.h.__keys__[entity.__id__] != null) return false; 410 | this.map.set(entity,data); 411 | this.count++; 412 | return true; 413 | } 414 | ,tryRemove: function(entity) { 415 | var o = this.map.h[entity.__id__]; 416 | if(null == o) return null; 417 | this.map.remove(entity); 418 | this.count--; 419 | return o; 420 | } 421 | ,__class__: edge.View 422 | }; 423 | edge.World = function(delta,schedule) { 424 | if(delta == null) delta = 16; 425 | this.engine = new edge.Engine(); 426 | this.frame = this.engine.createPhase(); 427 | this.physics = this.engine.createPhase(); 428 | this.render = this.engine.createPhase(); 429 | this.remainder = 0; 430 | this.running = false; 431 | this.delta = delta; 432 | if(null != schedule) this.schedule = schedule; else this.schedule = thx.core.Timer.frame; 433 | }; 434 | edge.World.__name__ = ["edge","World"]; 435 | edge.World.prototype = { 436 | start: function() { 437 | if(this.running) return; 438 | this.running = true; 439 | this.cancel = this.schedule($bind(this,this.run)); 440 | } 441 | ,run: function(t) { 442 | this.frame.update(t); 443 | var dt = t + this.remainder; 444 | while(dt > this.delta) { 445 | dt -= this.delta; 446 | this.physics.update(this.delta); 447 | } 448 | this.remainder = dt; 449 | this.render.update(t); 450 | } 451 | ,__class__: edge.World 452 | }; 453 | edge.core.NodeSystem = function(system) { 454 | this.system = system; 455 | }; 456 | edge.core.NodeSystem.__name__ = ["edge","core","NodeSystem"]; 457 | edge.core.NodeSystem.prototype = { 458 | __class__: edge.core.NodeSystem 459 | }; 460 | edge.core.NodeSystemIterator = function(node) { 461 | this.node = node; 462 | }; 463 | edge.core.NodeSystemIterator.__name__ = ["edge","core","NodeSystemIterator"]; 464 | edge.core.NodeSystemIterator.prototype = { 465 | hasNext: function() { 466 | return null != this.node; 467 | } 468 | ,next: function() { 469 | var system = this.node.system; 470 | this.node = this.node.next; 471 | return system; 472 | } 473 | ,__class__: edge.core.NodeSystemIterator 474 | }; 475 | var haxe = {}; 476 | haxe.IMap = function() { }; 477 | haxe.IMap.__name__ = ["haxe","IMap"]; 478 | haxe.ds = {}; 479 | haxe.ds.ObjectMap = function() { 480 | this.h = { }; 481 | this.h.__keys__ = { }; 482 | }; 483 | haxe.ds.ObjectMap.__name__ = ["haxe","ds","ObjectMap"]; 484 | haxe.ds.ObjectMap.__interfaces__ = [haxe.IMap]; 485 | haxe.ds.ObjectMap.prototype = { 486 | set: function(key,value) { 487 | var id = key.__id__ || (key.__id__ = ++haxe.ds.ObjectMap.count); 488 | this.h[id] = value; 489 | this.h.__keys__[id] = key; 490 | } 491 | ,remove: function(key) { 492 | var id = key.__id__; 493 | if(this.h.__keys__[id] == null) return false; 494 | delete(this.h[id]); 495 | delete(this.h.__keys__[id]); 496 | return true; 497 | } 498 | ,keys: function() { 499 | var a = []; 500 | for( var key in this.h.__keys__ ) { 501 | if(this.h.hasOwnProperty(key)) a.push(this.h.__keys__[key]); 502 | } 503 | return HxOverrides.iter(a); 504 | } 505 | ,__class__: haxe.ds.ObjectMap 506 | }; 507 | haxe.ds._StringMap = {}; 508 | haxe.ds._StringMap.StringMapIterator = function(map,keys) { 509 | this.map = map; 510 | this.keys = keys; 511 | this.index = 0; 512 | this.count = keys.length; 513 | }; 514 | haxe.ds._StringMap.StringMapIterator.__name__ = ["haxe","ds","_StringMap","StringMapIterator"]; 515 | haxe.ds._StringMap.StringMapIterator.prototype = { 516 | hasNext: function() { 517 | return this.index < this.count; 518 | } 519 | ,next: function() { 520 | return this.map.get(this.keys[this.index++]); 521 | } 522 | ,__class__: haxe.ds._StringMap.StringMapIterator 523 | }; 524 | haxe.ds.StringMap = function() { 525 | this.h = { }; 526 | }; 527 | haxe.ds.StringMap.__name__ = ["haxe","ds","StringMap"]; 528 | haxe.ds.StringMap.__interfaces__ = [haxe.IMap]; 529 | haxe.ds.StringMap.prototype = { 530 | set: function(key,value) { 531 | if(__map_reserved[key] != null) this.setReserved(key,value); else this.h[key] = value; 532 | } 533 | ,get: function(key) { 534 | if(__map_reserved[key] != null) return this.getReserved(key); 535 | return this.h[key]; 536 | } 537 | ,exists: function(key) { 538 | if(__map_reserved[key] != null) return this.existsReserved(key); 539 | return this.h.hasOwnProperty(key); 540 | } 541 | ,setReserved: function(key,value) { 542 | if(this.rh == null) this.rh = { }; 543 | this.rh["$" + key] = value; 544 | } 545 | ,getReserved: function(key) { 546 | if(this.rh == null) return null; else return this.rh["$" + key]; 547 | } 548 | ,existsReserved: function(key) { 549 | if(this.rh == null) return false; 550 | return this.rh.hasOwnProperty("$" + key); 551 | } 552 | ,remove: function(key) { 553 | if(__map_reserved[key] != null) { 554 | key = "$" + key; 555 | if(this.rh == null || !this.rh.hasOwnProperty(key)) return false; 556 | delete(this.rh[key]); 557 | return true; 558 | } else { 559 | if(!this.h.hasOwnProperty(key)) return false; 560 | delete(this.h[key]); 561 | return true; 562 | } 563 | } 564 | ,arrayKeys: function() { 565 | var out = []; 566 | for( var key in this.h ) { 567 | if(this.h.hasOwnProperty(key)) out.push(key); 568 | } 569 | if(this.rh != null) { 570 | for( var key in this.rh ) { 571 | if(key.charCodeAt(0) == 36) out.push(key.substr(1)); 572 | } 573 | } 574 | return out; 575 | } 576 | ,iterator: function() { 577 | return new haxe.ds._StringMap.StringMapIterator(this,this.arrayKeys()); 578 | } 579 | ,__class__: haxe.ds.StringMap 580 | }; 581 | var js = {}; 582 | js.Boot = function() { }; 583 | js.Boot.__name__ = ["js","Boot"]; 584 | js.Boot.getClass = function(o) { 585 | if((o instanceof Array) && o.__enum__ == null) return Array; else { 586 | var cl = o.__class__; 587 | if(cl != null) return cl; 588 | var name = js.Boot.__nativeClassName(o); 589 | if(name != null) return js.Boot.__resolveNativeClass(name); 590 | return null; 591 | } 592 | }; 593 | js.Boot.__string_rec = function(o,s) { 594 | if(o == null) return "null"; 595 | if(s.length >= 5) return "<...>"; 596 | var t = typeof(o); 597 | if(t == "function" && (o.__name__ || o.__ename__)) t = "object"; 598 | switch(t) { 599 | case "object": 600 | if(o instanceof Array) { 601 | if(o.__enum__) { 602 | if(o.length == 2) return o[0]; 603 | var str2 = o[0] + "("; 604 | s += "\t"; 605 | var _g1 = 2; 606 | var _g = o.length; 607 | while(_g1 < _g) { 608 | var i1 = _g1++; 609 | if(i1 != 2) str2 += "," + js.Boot.__string_rec(o[i1],s); else str2 += js.Boot.__string_rec(o[i1],s); 610 | } 611 | return str2 + ")"; 612 | } 613 | var l = o.length; 614 | var i; 615 | var str1 = "["; 616 | s += "\t"; 617 | var _g2 = 0; 618 | while(_g2 < l) { 619 | var i2 = _g2++; 620 | str1 += (i2 > 0?",":"") + js.Boot.__string_rec(o[i2],s); 621 | } 622 | str1 += "]"; 623 | return str1; 624 | } 625 | var tostr; 626 | try { 627 | tostr = o.toString; 628 | } catch( e ) { 629 | return "???"; 630 | } 631 | if(tostr != null && tostr != Object.toString && typeof(tostr) == "function") { 632 | var s2 = o.toString(); 633 | if(s2 != "[object Object]") return s2; 634 | } 635 | var k = null; 636 | var str = "{\n"; 637 | s += "\t"; 638 | var hasp = o.hasOwnProperty != null; 639 | for( var k in o ) { 640 | if(hasp && !o.hasOwnProperty(k)) { 641 | continue; 642 | } 643 | if(k == "prototype" || k == "__class__" || k == "__super__" || k == "__interfaces__" || k == "__properties__") { 644 | continue; 645 | } 646 | if(str.length != 2) str += ", \n"; 647 | str += s + k + " : " + js.Boot.__string_rec(o[k],s); 648 | } 649 | s = s.substring(1); 650 | str += "\n" + s + "}"; 651 | return str; 652 | case "function": 653 | return ""; 654 | case "string": 655 | return o; 656 | default: 657 | return String(o); 658 | } 659 | }; 660 | js.Boot.__interfLoop = function(cc,cl) { 661 | if(cc == null) return false; 662 | if(cc == cl) return true; 663 | var intf = cc.__interfaces__; 664 | if(intf != null) { 665 | var _g1 = 0; 666 | var _g = intf.length; 667 | while(_g1 < _g) { 668 | var i = _g1++; 669 | var i1 = intf[i]; 670 | if(i1 == cl || js.Boot.__interfLoop(i1,cl)) return true; 671 | } 672 | } 673 | return js.Boot.__interfLoop(cc.__super__,cl); 674 | }; 675 | js.Boot.__instanceof = function(o,cl) { 676 | if(cl == null) return false; 677 | switch(cl) { 678 | case Int: 679 | return (o|0) === o; 680 | case Float: 681 | return typeof(o) == "number"; 682 | case Bool: 683 | return typeof(o) == "boolean"; 684 | case String: 685 | return typeof(o) == "string"; 686 | case Array: 687 | return (o instanceof Array) && o.__enum__ == null; 688 | case Dynamic: 689 | return true; 690 | default: 691 | if(o != null) { 692 | if(typeof(cl) == "function") { 693 | if(o instanceof cl) return true; 694 | if(js.Boot.__interfLoop(js.Boot.getClass(o),cl)) return true; 695 | } else if(typeof(cl) == "object" && js.Boot.__isNativeObj(cl)) { 696 | if(o instanceof cl) return true; 697 | } 698 | } else return false; 699 | if(cl == Class && o.__name__ != null) return true; 700 | if(cl == Enum && o.__ename__ != null) return true; 701 | return o.__enum__ == cl; 702 | } 703 | }; 704 | js.Boot.__nativeClassName = function(o) { 705 | var name = js.Boot.__toStr.call(o).slice(8,-1); 706 | if(name == "Object" || name == "Function" || name == "Math" || name == "JSON") return null; 707 | return name; 708 | }; 709 | js.Boot.__isNativeObj = function(o) { 710 | return js.Boot.__nativeClassName(o) != null; 711 | }; 712 | js.Boot.__resolveNativeClass = function(name) { 713 | if(typeof window != "undefined") return window[name]; else return global[name]; 714 | }; 715 | var minicanvas = {}; 716 | minicanvas.MiniCanvas = function(width,height,scaleMode) { 717 | this.scaleMode = scaleMode; 718 | this.width = width; 719 | this.height = height; 720 | this.processScale(); 721 | this.startTime = performance.now(); 722 | this.events = new haxe.ds.StringMap(); 723 | this.init(); 724 | }; 725 | minicanvas.MiniCanvas.__name__ = ["minicanvas","MiniCanvas"]; 726 | minicanvas.MiniCanvas.envIsNode = function() { 727 | return typeof module !== 'undefined' && module.exports; 728 | }; 729 | minicanvas.MiniCanvas.create = function(width,height,scaleMode) { 730 | if(minicanvas.MiniCanvas.envIsNode()) return new minicanvas.NodeCanvas(width,height,scaleMode); else return new minicanvas.BrowserCanvas(width,height,scaleMode); 731 | }; 732 | minicanvas.MiniCanvas.prototype = { 733 | display: function(name) { 734 | this.deltaTime = performance.now() - this.startTime; 735 | if(!minicanvas.MiniCanvas.displayGenerationTime) console.log("generated \"" + name + "\" in " + thx.core.Floats.roundTo(this.deltaTime,2) + "ms"); 736 | this.nativeDisplay(name); 737 | return this; 738 | } 739 | ,clear: function() { 740 | this.ctx.clearRect(0,0,this.width,this.height); 741 | return this; 742 | } 743 | ,dot: function(x,y,radius,color) { 744 | if(radius == null) radius = 3.0; 745 | this.ctx.beginPath(); 746 | this.ctx.fillStyle = thx.color._RGBA.RGBA_Impl_.toString((function($this) { 747 | var $r; 748 | var t; 749 | { 750 | var _0 = color; 751 | if(null == _0) t = null; else t = _0; 752 | } 753 | $r = t != null?t:thx.color._RGBA.RGBA_Impl_.fromString("rgba(204,51,0,1)"); 754 | return $r; 755 | }(this))); 756 | this.ctx.arc(x,y,radius,0,Math.PI * 2,true); 757 | this.ctx.fill(); 758 | return this; 759 | } 760 | ,getDevicePixelRatio: function() { 761 | throw "abstract method getDevicePixelRatio()"; 762 | } 763 | ,getBackingStoreRatio: function() { 764 | throw "abstract method getBackingStoreRatio()"; 765 | } 766 | ,init: function() { 767 | throw "abstract method init()"; 768 | return; 769 | } 770 | ,nativeDisplay: function(name) { 771 | throw "abstract method nativeDisplay()"; 772 | return; 773 | } 774 | ,processScale: function() { 775 | var _g = this.scaleMode; 776 | switch(_g[1]) { 777 | case 1: 778 | var ratio = this.getDevicePixelRatio() / this.getBackingStoreRatio(); 779 | if(ratio != 1) this.scaleMode = minicanvas.ScaleMode.Scaled(ratio); else this.scaleMode = minicanvas.ScaleMode.NoScale; 780 | break; 781 | default: 782 | } 783 | } 784 | ,__class__: minicanvas.MiniCanvas 785 | }; 786 | minicanvas.ScaleMode = { __ename__ : true, __constructs__ : ["NoScale","Auto","Scaled"] }; 787 | minicanvas.ScaleMode.NoScale = ["NoScale",0]; 788 | minicanvas.ScaleMode.NoScale.__enum__ = minicanvas.ScaleMode; 789 | minicanvas.ScaleMode.Auto = ["Auto",1]; 790 | minicanvas.ScaleMode.Auto.__enum__ = minicanvas.ScaleMode; 791 | minicanvas.ScaleMode.Scaled = function(v) { var $x = ["Scaled",2,v]; $x.__enum__ = minicanvas.ScaleMode; return $x; }; 792 | minicanvas.BrowserCanvas = function(width,height,scaleMode) { 793 | this.isNode = false; 794 | this.isBrowser = true; 795 | if(null == scaleMode) scaleMode = minicanvas.BrowserCanvas.defaultScaleMode; 796 | minicanvas.MiniCanvas.call(this,width,height,scaleMode); 797 | }; 798 | minicanvas.BrowserCanvas.__name__ = ["minicanvas","BrowserCanvas"]; 799 | minicanvas.BrowserCanvas.devicePixelRatio = function() { 800 | return window.devicePixelRatio || 1; 801 | }; 802 | minicanvas.BrowserCanvas.backingStoreRatio = function() { 803 | if(minicanvas.BrowserCanvas._backingStoreRatio == 0) { 804 | var canvas; 805 | var _this = window.document; 806 | canvas = _this.createElement("canvas"); 807 | var context = canvas.getContext("2d"); 808 | minicanvas.BrowserCanvas._backingStoreRatio = (function(c) { 809 | return c.webkitBackingStorePixelRatio || 810 | c.mozBackingStorePixelRatio || 811 | c.msBackingStorePixelRatio || 812 | c.oBackingStorePixelRatio || 813 | c.backingStorePixelRatio || 1; 814 | })(context); 815 | } 816 | return minicanvas.BrowserCanvas._backingStoreRatio; 817 | }; 818 | minicanvas.BrowserCanvas.__super__ = minicanvas.MiniCanvas; 819 | minicanvas.BrowserCanvas.prototype = $extend(minicanvas.MiniCanvas.prototype,{ 820 | append: function(name) { 821 | var figure = window.document.createElement("figure"); 822 | var caption = window.document.createElement("figcaption"); 823 | figure.className = "minicanvas"; 824 | figure.appendChild(this.canvas); 825 | caption.innerHTML = thx.core.Strings.humanize(name) + (minicanvas.MiniCanvas.displayGenerationTime?" (" + thx.core.Floats.roundTo(this.deltaTime,2) + "ms)":""); 826 | figure.appendChild(caption); 827 | minicanvas.BrowserCanvas.parentNode.appendChild(figure); 828 | if(null != this._keyUp || null != this._keyDown) this.canvas.focus(); 829 | } 830 | ,init: function() { 831 | var _this = window.document; 832 | this.canvas = _this.createElement("canvas"); 833 | { 834 | var _g = this.scaleMode; 835 | switch(_g[1]) { 836 | case 2: 837 | var v = _g[2]; 838 | this.canvas.width = Math.round(this.width * v); 839 | this.canvas.height = Math.round(this.height * v); 840 | this.canvas.style.width = "" + this.width + "px"; 841 | this.canvas.style.height = "" + this.height + "px"; 842 | this.ctx = this.canvas.getContext("2d"); 843 | this.ctx.scale(v,v); 844 | break; 845 | default: 846 | this.canvas.width = this.width; 847 | this.canvas.height = this.height; 848 | this.ctx = this.canvas.getContext("2d"); 849 | } 850 | } 851 | } 852 | ,getDevicePixelRatio: function() { 853 | return minicanvas.BrowserCanvas.devicePixelRatio(); 854 | } 855 | ,getBackingStoreRatio: function() { 856 | return minicanvas.BrowserCanvas.backingStoreRatio(); 857 | } 858 | ,nativeDisplay: function(name) { 859 | this.append(name); 860 | } 861 | ,__class__: minicanvas.BrowserCanvas 862 | }); 863 | minicanvas.NodeCanvas = function(width,height,scaleMode) { 864 | this.hasFrames = false; 865 | this.isNode = true; 866 | this.isBrowser = false; 867 | if(null == scaleMode) scaleMode = minicanvas.NodeCanvas.defaultScaleMode; 868 | minicanvas.MiniCanvas.call(this,width,height,scaleMode); 869 | }; 870 | minicanvas.NodeCanvas.__name__ = ["minicanvas","NodeCanvas"]; 871 | minicanvas.NodeCanvas.create = function(width,height,scaleMode) { 872 | return new minicanvas.MiniCanvas(width,height,scaleMode); 873 | }; 874 | minicanvas.NodeCanvas.__super__ = minicanvas.MiniCanvas; 875 | minicanvas.NodeCanvas.prototype = $extend(minicanvas.MiniCanvas.prototype,{ 876 | save: function(name) { 877 | var encoder = this.ensureEncoder(); 878 | encoder.addFrame(this.ctx); 879 | encoder.save(name,function(file) { 880 | console.log("saved " + file); 881 | }); 882 | } 883 | ,init: function() { 884 | var Canvas = require("canvas"); 885 | { 886 | var _g = this.scaleMode; 887 | switch(_g[1]) { 888 | case 2: 889 | var v = _g[2]; 890 | this.canvas = new Canvas(this.width * v,this.height * v); 891 | this.ctx = this.canvas.getContext("2d"); 892 | this.ctx.scale(v,v); 893 | break; 894 | default: 895 | this.canvas = new Canvas(this.width,this.height); 896 | this.ctx = this.canvas.getContext("2d"); 897 | } 898 | } 899 | } 900 | ,getDevicePixelRatio: function() { 901 | return 1.0; 902 | } 903 | ,getBackingStoreRatio: function() { 904 | return 1.0; 905 | } 906 | ,nativeDisplay: function(name) { 907 | this.save(name); 908 | } 909 | ,ensureEncoder: function() { 910 | if(null != this.encoder) return this.encoder; 911 | if(this.hasFrames) return this.encoder = new minicanvas.node.GifEncoder(this.width,this.height); else return this.encoder = new minicanvas.node.PNGEncoder(this.canvas); 912 | } 913 | ,__class__: minicanvas.NodeCanvas 914 | }); 915 | minicanvas.node = {}; 916 | minicanvas.node.IEncoder = function() { }; 917 | minicanvas.node.IEncoder.__name__ = ["minicanvas","node","IEncoder"]; 918 | minicanvas.node.IEncoder.prototype = { 919 | __class__: minicanvas.node.IEncoder 920 | }; 921 | minicanvas.node.GifEncoder = function(width,height) { 922 | this.frames = 0; 923 | this.encoder = (function(w, h, self) { 924 | var GIFEncoder = require('gifencoder'), 925 | encoder = new GIFEncoder(w, h); 926 | self.stream = encoder.createReadStream(); 927 | encoder.start(); 928 | encoder.setRepeat(0); 929 | encoder.setDelay(50); 930 | encoder.setQuality(10); 931 | return encoder; 932 | })(width,height,this); 933 | }; 934 | minicanvas.node.GifEncoder.__name__ = ["minicanvas","node","GifEncoder"]; 935 | minicanvas.node.GifEncoder.__interfaces__ = [minicanvas.node.IEncoder]; 936 | minicanvas.node.GifEncoder.prototype = { 937 | addFrame: function(ctx) { 938 | this.encoder.addFrame(ctx); 939 | this.frames++; 940 | } 941 | ,save: function(name,callback) { 942 | this.stream.pipe(require("fs").createWriteStream("" + minicanvas.NodeCanvas.imagePath + "/" + name + ".gif")); 943 | callback("" + name + ".gif (frames " + this.frames + ")"); 944 | } 945 | ,__class__: minicanvas.node.GifEncoder 946 | }; 947 | minicanvas.node.PNGEncoder = function(canvas) { 948 | this.canvas = canvas; 949 | }; 950 | minicanvas.node.PNGEncoder.__name__ = ["minicanvas","node","PNGEncoder"]; 951 | minicanvas.node.PNGEncoder.__interfaces__ = [minicanvas.node.IEncoder]; 952 | minicanvas.node.PNGEncoder.prototype = { 953 | addFrame: function(ctx) { 954 | } 955 | ,save: function(name,callback) { 956 | var fs = require("fs"); 957 | var out = fs.createWriteStream("" + minicanvas.NodeCanvas.imagePath + "/" + name + ".png"); 958 | var stream = this.canvas.pngStream(); 959 | stream.on("data",function(chunk) { 960 | out.write(chunk); 961 | }); 962 | stream.on("end",function(_) { 963 | callback("" + name + ".png"); 964 | }); 965 | } 966 | ,__class__: minicanvas.node.PNGEncoder 967 | }; 968 | var thx = {}; 969 | thx.color = {}; 970 | thx.color._RGB = {}; 971 | thx.color._RGB.RGB_Impl_ = {}; 972 | thx.color._RGB.RGB_Impl_.__name__ = ["thx","color","_RGB","RGB_Impl_"]; 973 | thx.color._RGB.RGB_Impl_.create = function(red,green,blue) { 974 | return (red & 255) << 16 | (green & 255) << 8 | blue & 255; 975 | }; 976 | thx.color._RGB.RGB_Impl_.fromInts = function(arr) { 977 | thx.core.ArrayInts.resize(arr,3); 978 | return thx.color._RGB.RGB_Impl_.create(arr[0],arr[1],arr[2]); 979 | }; 980 | thx.color._RGB.RGB_Impl_.withAlpha = function(this1,alpha) { 981 | return thx.color._RGBA.RGBA_Impl_.fromInts([thx.color._RGB.RGB_Impl_.get_red(this1),thx.color._RGB.RGB_Impl_.get_green(this1),thx.color._RGB.RGB_Impl_.get_blue(this1),alpha]); 982 | }; 983 | thx.color._RGB.RGB_Impl_.toRGBA = function(this1) { 984 | return thx.color._RGB.RGB_Impl_.withAlpha(this1,255); 985 | }; 986 | thx.color._RGB.RGB_Impl_.get_red = function(this1) { 987 | return this1 >> 16 & 255; 988 | }; 989 | thx.color._RGB.RGB_Impl_.get_green = function(this1) { 990 | return this1 >> 8 & 255; 991 | }; 992 | thx.color._RGB.RGB_Impl_.get_blue = function(this1) { 993 | return this1 & 255; 994 | }; 995 | thx.color._RGBA = {}; 996 | thx.color._RGBA.RGBA_Impl_ = {}; 997 | thx.color._RGBA.RGBA_Impl_.__name__ = ["thx","color","_RGBA","RGBA_Impl_"]; 998 | thx.color._RGBA.RGBA_Impl_.create = function(red,green,blue,alpha) { 999 | return (red & 255) << 24 | (green & 255) << 16 | (blue & 255) << 8 | alpha & 255; 1000 | }; 1001 | thx.color._RGBA.RGBA_Impl_.fromInts = function(arr) { 1002 | thx.core.ArrayInts.resize(arr,4); 1003 | return thx.color._RGBA.RGBA_Impl_.create(arr[0],arr[1],arr[2],arr[3]); 1004 | }; 1005 | thx.color._RGBA.RGBA_Impl_.fromString = function(color) { 1006 | var info = thx.color.parse.ColorParser.parseHex(color); 1007 | if(null == info) info = thx.color.parse.ColorParser.parseColor(color); 1008 | if(null == info) return null; 1009 | try { 1010 | var _g = info.name; 1011 | switch(_g) { 1012 | case "rgb": 1013 | return thx.color._RGB.RGB_Impl_.toRGBA(thx.color._RGB.RGB_Impl_.fromInts(thx.color.parse.ColorParser.getInt8Channels(info.channels,3))); 1014 | case "rgba": 1015 | return thx.color._RGBA.RGBA_Impl_.create(thx.color.parse.ColorParser.getInt8Channel(info.channels[0]),thx.color.parse.ColorParser.getInt8Channel(info.channels[1]),thx.color.parse.ColorParser.getInt8Channel(info.channels[2]),Math.round(thx.color.parse.ColorParser.getFloatChannel(info.channels[3]) * 255)); 1016 | default: 1017 | return null; 1018 | } 1019 | } catch( e ) { 1020 | return null; 1021 | } 1022 | }; 1023 | thx.color._RGBA.RGBA_Impl_.toString = function(this1) { 1024 | return "rgba(" + (this1 >> 24 & 255) + "," + (this1 >> 16 & 255) + "," + (this1 >> 8 & 255) + "," + (this1 & 255) / 255 + ")"; 1025 | }; 1026 | thx.color.parse = {}; 1027 | thx.color.parse.ColorParser = function() { 1028 | this.pattern_color = new EReg("^\\s*([^(]+)\\s*\\(([^)]*)\\)\\s*$","i"); 1029 | this.pattern_channel = new EReg("^\\s*(\\d*.\\d+|\\d+)(%|deg|rad)?\\s*$","i"); 1030 | }; 1031 | thx.color.parse.ColorParser.__name__ = ["thx","color","parse","ColorParser"]; 1032 | thx.color.parse.ColorParser.parseColor = function(s) { 1033 | return thx.color.parse.ColorParser.parser.processColor(s); 1034 | }; 1035 | thx.color.parse.ColorParser.parseHex = function(s) { 1036 | return thx.color.parse.ColorParser.parser.processHex(s); 1037 | }; 1038 | thx.color.parse.ColorParser.getInt8Channels = function(channels,length) { 1039 | if(length != channels.length) throw "invalid number of channels, expected " + length + " but it is " + channels.length; 1040 | return channels.map(thx.color.parse.ColorParser.getInt8Channel); 1041 | }; 1042 | thx.color.parse.ColorParser.getFloatChannel = function(channel,useInt8) { 1043 | if(useInt8 == null) useInt8 = true; 1044 | switch(channel[1]) { 1045 | case 5: 1046 | var v = channel[2]; 1047 | if(v) return 1; else return 0; 1048 | break; 1049 | case 1: 1050 | var v1 = channel[2]; 1051 | return v1; 1052 | case 4: 1053 | var v2 = channel[2]; 1054 | return v2; 1055 | case 2: 1056 | var v3 = channel[2]; 1057 | return v3; 1058 | case 3: 1059 | var v4 = channel[2]; 1060 | if(useInt8) return v4 / 255; else { 1061 | var v5 = channel[2]; 1062 | return v5; 1063 | } 1064 | break; 1065 | case 0: 1066 | var v6 = channel[2]; 1067 | return v6 / 100; 1068 | } 1069 | }; 1070 | thx.color.parse.ColorParser.getInt8Channel = function(channel) { 1071 | switch(channel[1]) { 1072 | case 5: 1073 | var v = channel[2]; 1074 | if(v) return 1; else return 0; 1075 | break; 1076 | case 3: 1077 | var v1 = channel[2]; 1078 | return v1; 1079 | case 0: 1080 | var v2 = channel[2]; 1081 | return Math.round(255 * v2 / 100); 1082 | default: 1083 | throw "unable to extract a valid int8 value"; 1084 | } 1085 | }; 1086 | thx.color.parse.ColorParser.prototype = { 1087 | processHex: function(s) { 1088 | if(!thx.color.parse.ColorParser.isPureHex.match(s)) { 1089 | if(HxOverrides.substr(s,0,1) == "#") { 1090 | if(s.length == 4) s = s.charAt(1) + s.charAt(1) + s.charAt(2) + s.charAt(2) + s.charAt(3) + s.charAt(3); else if(s.length == 5) s = s.charAt(1) + s.charAt(1) + s.charAt(2) + s.charAt(2) + s.charAt(3) + s.charAt(3) + s.charAt(4) + s.charAt(4); else s = HxOverrides.substr(s,1,null); 1091 | } else if(HxOverrides.substr(s,0,2) == "0x") s = HxOverrides.substr(s,2,null); else return null; 1092 | } 1093 | var channels = []; 1094 | while(s.length > 0) { 1095 | channels.push(thx.color.parse.ChannelInfo.CIInt8(Std.parseInt("0x" + HxOverrides.substr(s,0,2)))); 1096 | s = HxOverrides.substr(s,2,null); 1097 | } 1098 | if(channels.length == 4) return new thx.color.parse.ColorInfo("rgba",channels.slice(1).concat([channels[0]])); else return new thx.color.parse.ColorInfo("rgb",channels); 1099 | } 1100 | ,processColor: function(s) { 1101 | if(!this.pattern_color.match(s)) return null; 1102 | var name = this.pattern_color.matched(1); 1103 | if(null == name) return null; 1104 | name = name.toLowerCase(); 1105 | var m2 = this.pattern_color.matched(2); 1106 | var s_channels; 1107 | if(null == m2) s_channels = []; else s_channels = m2.split(","); 1108 | var channels = []; 1109 | var channel; 1110 | var _g = 0; 1111 | while(_g < s_channels.length) { 1112 | var s_channel = s_channels[_g]; 1113 | ++_g; 1114 | channel = this.processChannel(s_channel); 1115 | if(null == channel) return null; 1116 | channels.push(channel); 1117 | } 1118 | return new thx.color.parse.ColorInfo(name,channels); 1119 | } 1120 | ,processChannel: function(s) { 1121 | if(!this.pattern_channel.match(s)) return null; 1122 | var value = this.pattern_channel.matched(1); 1123 | var unit = this.pattern_channel.matched(2); 1124 | if(unit == null) unit = ""; 1125 | try { 1126 | switch(unit) { 1127 | case "%": 1128 | if(thx.core.Floats.canParse(value)) return thx.color.parse.ChannelInfo.CIPercent(thx.core.Floats.parse(value)); else return null; 1129 | break; 1130 | case "deg": 1131 | if(thx.core.Floats.canParse(value)) return thx.color.parse.ChannelInfo.CIDegree(thx.core.Floats.parse(value)); else return null; 1132 | break; 1133 | case "DEG": 1134 | if(thx.core.Floats.canParse(value)) return thx.color.parse.ChannelInfo.CIDegree(thx.core.Floats.parse(value)); else return null; 1135 | break; 1136 | case "rad": 1137 | if(thx.core.Floats.canParse(value)) return thx.color.parse.ChannelInfo.CIDegree(thx.core.Floats.parse(value) * 180 / Math.PI); else return null; 1138 | break; 1139 | case "RAD": 1140 | if(thx.core.Floats.canParse(value)) return thx.color.parse.ChannelInfo.CIDegree(thx.core.Floats.parse(value) * 180 / Math.PI); else return null; 1141 | break; 1142 | case "": 1143 | if(thx.core.Ints.canParse(value)) { 1144 | var i = thx.core.Ints.parse(value); 1145 | if(i == 0) return thx.color.parse.ChannelInfo.CIBool(false); else if(i == 1) return thx.color.parse.ChannelInfo.CIBool(true); else if(i < 256) return thx.color.parse.ChannelInfo.CIInt8(i); else return thx.color.parse.ChannelInfo.CIInt(i); 1146 | } else if(thx.core.Floats.canParse(value)) return thx.color.parse.ChannelInfo.CIFloat(thx.core.Floats.parse(value)); else return null; 1147 | break; 1148 | default: 1149 | return null; 1150 | } 1151 | } catch( e ) { 1152 | return null; 1153 | } 1154 | } 1155 | ,__class__: thx.color.parse.ColorParser 1156 | }; 1157 | thx.color.parse.ColorInfo = function(name,channels) { 1158 | this.name = name; 1159 | this.channels = channels; 1160 | }; 1161 | thx.color.parse.ColorInfo.__name__ = ["thx","color","parse","ColorInfo"]; 1162 | thx.color.parse.ColorInfo.prototype = { 1163 | __class__: thx.color.parse.ColorInfo 1164 | }; 1165 | thx.color.parse.ChannelInfo = { __ename__ : true, __constructs__ : ["CIPercent","CIFloat","CIDegree","CIInt8","CIInt","CIBool"] }; 1166 | thx.color.parse.ChannelInfo.CIPercent = function(value) { var $x = ["CIPercent",0,value]; $x.__enum__ = thx.color.parse.ChannelInfo; return $x; }; 1167 | thx.color.parse.ChannelInfo.CIFloat = function(value) { var $x = ["CIFloat",1,value]; $x.__enum__ = thx.color.parse.ChannelInfo; return $x; }; 1168 | thx.color.parse.ChannelInfo.CIDegree = function(value) { var $x = ["CIDegree",2,value]; $x.__enum__ = thx.color.parse.ChannelInfo; return $x; }; 1169 | thx.color.parse.ChannelInfo.CIInt8 = function(value) { var $x = ["CIInt8",3,value]; $x.__enum__ = thx.color.parse.ChannelInfo; return $x; }; 1170 | thx.color.parse.ChannelInfo.CIInt = function(value) { var $x = ["CIInt",4,value]; $x.__enum__ = thx.color.parse.ChannelInfo; return $x; }; 1171 | thx.color.parse.ChannelInfo.CIBool = function(value) { var $x = ["CIBool",5,value]; $x.__enum__ = thx.color.parse.ChannelInfo; return $x; }; 1172 | thx.core = {}; 1173 | thx.core.ArrayInts = function() { }; 1174 | thx.core.ArrayInts.__name__ = ["thx","core","ArrayInts"]; 1175 | thx.core.ArrayInts.resize = function(array,length,fill) { 1176 | if(fill == null) fill = 0; 1177 | while(array.length < length) array.push(fill); 1178 | array.splice(length,array.length - length); 1179 | return array; 1180 | }; 1181 | thx.core.Floats = function() { }; 1182 | thx.core.Floats.__name__ = ["thx","core","Floats"]; 1183 | thx.core.Floats.canParse = function(s) { 1184 | return thx.core.Floats.pattern_parse.match(s); 1185 | }; 1186 | thx.core.Floats.parse = function(s) { 1187 | if(s.substring(0,1) == "+") s = s.substring(1); 1188 | return parseFloat(s); 1189 | }; 1190 | thx.core.Floats.roundTo = function(f,decimals) { 1191 | var p = Math.pow(10,decimals); 1192 | return Math.round(f * p) / p; 1193 | }; 1194 | thx.core.Functions = function() { }; 1195 | thx.core.Functions.__name__ = ["thx","core","Functions"]; 1196 | thx.core.Functions.noop = function() { 1197 | }; 1198 | thx.core.Ints = function() { }; 1199 | thx.core.Ints.__name__ = ["thx","core","Ints"]; 1200 | thx.core.Ints.canParse = function(s) { 1201 | return thx.core.Ints.pattern_parse.match(s); 1202 | }; 1203 | thx.core.Ints.parse = function(s,base) { 1204 | var v = parseInt(s,base); 1205 | if(isNaN(v)) return null; else return v; 1206 | }; 1207 | thx.core.Strings = function() { }; 1208 | thx.core.Strings.__name__ = ["thx","core","Strings"]; 1209 | thx.core.Strings.humanize = function(s) { 1210 | return StringTools.replace(thx.core.Strings.underscore(s),"_"," "); 1211 | }; 1212 | thx.core.Strings.underscore = function(s) { 1213 | s = new EReg("::","g").replace(s,"/"); 1214 | s = new EReg("([A-Z]+)([A-Z][a-z])","g").replace(s,"$1_$2"); 1215 | s = new EReg("([a-z\\d])([A-Z])","g").replace(s,"$1_$2"); 1216 | s = new EReg("-","g").replace(s,"_"); 1217 | return s.toLowerCase(); 1218 | }; 1219 | thx.core.Timer = function() { }; 1220 | thx.core.Timer.__name__ = ["thx","core","Timer"]; 1221 | thx.core.Timer.frame = function(callback) { 1222 | var cancelled = false; 1223 | var f = thx.core.Functions.noop; 1224 | var current = performance.now(); 1225 | var next; 1226 | f = function() { 1227 | if(cancelled) return; 1228 | next = performance.now(); 1229 | callback(next - current); 1230 | current = next; 1231 | requestAnimationFrame(f); 1232 | }; 1233 | requestAnimationFrame(f); 1234 | return function() { 1235 | cancelled = true; 1236 | }; 1237 | }; 1238 | var $_, $fid = 0; 1239 | function $bind(o,m) { if( m == null ) return null; if( m.__id__ == null ) m.__id__ = $fid++; var f; if( o.hx__closures__ == null ) o.hx__closures__ = {}; else f = o.hx__closures__[m.__id__]; if( f == null ) { f = function(){ return f.method.apply(f.scope, arguments); }; f.scope = o; f.method = m; o.hx__closures__[m.__id__] = f; } return f; } 1240 | String.prototype.__class__ = String; 1241 | String.__name__ = ["String"]; 1242 | Array.__name__ = ["Array"]; 1243 | Date.prototype.__class__ = Date; 1244 | Date.__name__ = ["Date"]; 1245 | var Int = { __name__ : ["Int"]}; 1246 | var Dynamic = { __name__ : ["Dynamic"]}; 1247 | var Float = Number; 1248 | Float.__name__ = ["Float"]; 1249 | var Bool = Boolean; 1250 | Bool.__ename__ = ["Bool"]; 1251 | var Class = { __name__ : ["Class"]}; 1252 | var Enum = { }; 1253 | if(Array.prototype.map == null) Array.prototype.map = function(f) { 1254 | var a = []; 1255 | var _g1 = 0; 1256 | var _g = this.length; 1257 | while(_g1 < _g) { 1258 | var i = _g1++; 1259 | a[i] = f(this[i]); 1260 | } 1261 | return a; 1262 | }; 1263 | var __map_reserved = {} 1264 | var scope = ("undefined" !== typeof window && window) || ("undefined" !== typeof global && global) || this; 1265 | if(!scope.setImmediate) scope.setImmediate = function(callback) { 1266 | scope.setTimeout(callback,0); 1267 | }; 1268 | var lastTime = 0; 1269 | var vendors = ["webkit","moz"]; 1270 | var x = 0; 1271 | while(x < vendors.length && !scope.requestAnimationFrame) { 1272 | scope.requestAnimationFrame = scope[vendors[x] + "RequestAnimationFrame"]; 1273 | scope.cancelAnimationFrame = scope[vendors[x] + "CancelAnimationFrame"] || scope[vendors[x] + "CancelRequestAnimationFrame"]; 1274 | x++; 1275 | } 1276 | if(!scope.requestAnimationFrame) scope.requestAnimationFrame = function(callback1) { 1277 | var currTime = new Date().getTime(); 1278 | var timeToCall = Math.max(0,16 - (currTime - lastTime)); 1279 | var id = scope.setTimeout(function() { 1280 | callback1(currTime + timeToCall); 1281 | },timeToCall); 1282 | lastTime = currTime + timeToCall; 1283 | return id; 1284 | }; 1285 | if(!scope.cancelAnimationFrame) scope.cancelAnimationFrame = function(id1) { 1286 | scope.clearTimeout(id1); 1287 | }; 1288 | if(typeof(scope.performance) == "undefined") scope.performance = { }; 1289 | if(typeof(scope.performance.now) == "undefined") { 1290 | var nowOffset = new Date().getTime(); 1291 | if(scope.performance.timing && scope.performance.timing.navigationStart) nowOffset = scope.performance.timing.navigationStart; 1292 | var now = function() { 1293 | return new Date() - nowOffset; 1294 | }; 1295 | scope.performance.now = now; 1296 | } 1297 | Game.width = 200; 1298 | Game.height = 200; 1299 | haxe.ds.ObjectMap.count = 0; 1300 | js.Boot.__toStr = {}.toString; 1301 | minicanvas.MiniCanvas.displayGenerationTime = false; 1302 | minicanvas.BrowserCanvas._backingStoreRatio = 0; 1303 | minicanvas.BrowserCanvas.defaultScaleMode = minicanvas.ScaleMode.Auto; 1304 | minicanvas.BrowserCanvas.parentNode = typeof document != 'undefined' && document.body; 1305 | minicanvas.NodeCanvas.defaultScaleMode = minicanvas.ScaleMode.NoScale; 1306 | minicanvas.NodeCanvas.imagePath = "images"; 1307 | thx.color.parse.ColorParser.parser = new thx.color.parse.ColorParser(); 1308 | thx.color.parse.ColorParser.isPureHex = new EReg("^([0-9a-f]{2}){3,4}$","i"); 1309 | thx.core.Floats.pattern_parse = new EReg("^(\\+|-)?\\d+(\\.\\d+)?(e-?\\d+)?$",""); 1310 | thx.core.Ints.pattern_parse = new EReg("^[+-]?(\\d+|0x[0-9A-F]+)$","i"); 1311 | Game.main(); 1312 | })(typeof console != "undefined" ? console : {log:function(){}}); 1313 | --------------------------------------------------------------------------------