├── .npmignore ├── src ├── gasm │ ├── core │ │ ├── api │ │ │ ├── IDisposable.hx │ │ │ └── singnals │ │ │ │ └── TResize.hx │ │ ├── math │ │ │ └── geom │ │ │ │ ├── Coords.hx │ │ │ │ ├── Point.hx │ │ │ │ ├── Box.hx │ │ │ │ ├── Area.hx │ │ │ │ ├── Vector.hx │ │ │ │ ├── Rectangle.hx │ │ │ │ └── Path.hx │ │ ├── enums │ │ │ ├── ScaleType.hx │ │ │ ├── Anchor.hx │ │ │ ├── LayoutType.hx │ │ │ ├── Orientation.hx │ │ │ ├── EventType.hx │ │ │ ├── SystemType.hx │ │ │ └── ComponentType.hx │ │ ├── utils │ │ │ ├── texture │ │ │ │ ├── ParseResult.hx │ │ │ │ ├── TextureData.hx │ │ │ │ ├── SpritesheetFrame.hx │ │ │ │ ├── TextureMeta.hx │ │ │ │ ├── TextureFrame.hx │ │ │ │ └── TexturePackerImport.hx │ │ │ ├── GeomUtils.hx │ │ │ ├── Log.hx │ │ │ ├── DynamicObject.hx │ │ │ ├── SignalConnection.hx │ │ │ ├── Assert.hx │ │ │ ├── Signal0.hx │ │ │ ├── Signal1.hx │ │ │ ├── Signal2.hx │ │ │ ├── MathUtils.hx │ │ │ ├── SignalBase.hx │ │ │ └── StringUtils.hx │ │ ├── events │ │ │ ├── api │ │ │ │ └── IEvent.hx │ │ │ ├── ResizeEvent.hx │ │ │ └── InteractionEvent.hx │ │ ├── Context.hx │ │ ├── IEngine.hx │ │ ├── ISystem.hx │ │ ├── System.hx │ │ ├── data │ │ │ └── TextConfig.hx │ │ ├── components │ │ │ ├── SoundModelComponent.hx │ │ │ ├── TextModelComponent.hx │ │ │ ├── OffsetComponent.hx │ │ │ ├── FadeComponent.hx │ │ │ ├── TweenComponent.hx │ │ │ ├── AppModelComponent.hx │ │ │ ├── SceneModelComponent.hx │ │ │ ├── SpriteModelComponent.hx │ │ │ ├── ThreeDModelComponent.hx │ │ │ └── LayoutComponent.hx │ │ ├── systems │ │ │ ├── ActorSystem.hx │ │ │ └── CoreSystem.hx │ │ ├── macros │ │ │ └── ComponentMacros.hx │ │ ├── Engine.hx │ │ ├── Component.hx │ │ └── Entity.hx │ ├── assets │ │ ├── FileEntry.hx │ │ └── Loader.hx │ ├── extra │ │ ├── components │ │ │ ├── FPSDisplayComponent.hx │ │ │ ├── FPSComponent.hx │ │ │ └── BounceAroundComponent.hx │ │ └── behaviors │ │ │ └── BounceBehavior.hx │ └── system │ │ └── components │ │ ├── PressSoundComponent.hx │ │ ├── PosFollowComponent.hx │ │ ├── ScaleFollowComponent.hx │ │ └── MouseFollowerComponent.hx └── jasper │ ├── Util.hx │ ├── Symbol.hx │ ├── Strength.hx │ ├── Errors.hx │ ├── Value.hx │ ├── Constraint.hx │ ├── Variable.hx │ ├── Solver.hx │ ├── Term.hx │ ├── Row.hx │ └── Expression.hx ├── .gitignore ├── .npmrc ├── tests.hxml ├── test ├── TestMain.hx └── gasm │ └── core │ ├── utils │ └── texture │ │ └── TexturePackerImportTest.hx │ ├── EntityTest.hx │ ├── EngineTest.hx │ └── EntityComponentTest.hx ├── haxelib.json ├── .travis.yml ├── package.json ├── LICENSE ├── GASM.hxproj ├── .gitlab-ci.yml └── README.md /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .gitlab-ci.yml 3 | .npmrc 4 | .idea 5 | *.iml -------------------------------------------------------------------------------- /src/gasm/core/api/IDisposable.hx: -------------------------------------------------------------------------------- 1 | package gasm.core.api; 2 | interface IDisposable { 3 | function dispose():Void; 4 | } -------------------------------------------------------------------------------- /src/gasm/core/math/geom/Coords.hx: -------------------------------------------------------------------------------- 1 | package gasm.core.math.geom; 2 | typedef Coords = { 3 | x:Int, 4 | y:Int 5 | } 6 | -------------------------------------------------------------------------------- /src/gasm/core/math/geom/Point.hx: -------------------------------------------------------------------------------- 1 | package gasm.core.math.geom; 2 | typedef Point = { 3 | x:Float, 4 | y:Float 5 | } 6 | -------------------------------------------------------------------------------- /src/gasm/core/enums/ScaleType.hx: -------------------------------------------------------------------------------- 1 | package gasm.core.enums; 2 | 3 | enum ScaleType { 4 | FIT; 5 | PROPORTIONAL; 6 | CROP; 7 | } 8 | -------------------------------------------------------------------------------- /src/gasm/core/api/singnals/TResize.hx: -------------------------------------------------------------------------------- 1 | package gasm.core.api.singnals; 2 | typedef TResize = { 3 | width:Float, 4 | height:Float, 5 | } -------------------------------------------------------------------------------- /src/gasm/core/enums/Anchor.hx: -------------------------------------------------------------------------------- 1 | package gasm.core.enums; 2 | 3 | enum Anchor { 4 | TOP; 5 | BOTTOM; 6 | LEFT; 7 | RIGHT; 8 | NONE; 9 | } 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.iml 3 | node_modules 4 | .haxelib 5 | .history 6 | .temp 7 | /bin 8 | /haxe_libraries 9 | /.vscode 10 | /build 11 | *.log -------------------------------------------------------------------------------- /src/gasm/core/utils/texture/ParseResult.hx: -------------------------------------------------------------------------------- 1 | package gasm.core.utils.texture; 2 | 3 | typedef ParseResult = { 4 | frames:Array, 5 | } 6 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | always-auth=true 2 | registry=https://packagecloud.io/hacksawstudios/client/npm/ 3 | //packagecloud.io/hacksawstudios/client/npm/:_authToken=${PACKAGECLOUD_TOKEN} -------------------------------------------------------------------------------- /src/gasm/core/enums/LayoutType.hx: -------------------------------------------------------------------------------- 1 | package gasm.core.enums; 2 | enum LayoutType { 3 | TOP; 4 | MIDDLE; 5 | BOTTOM; 6 | LEFT; 7 | CENTER; 8 | RIGHT; 9 | } -------------------------------------------------------------------------------- /src/gasm/core/math/geom/Box.hx: -------------------------------------------------------------------------------- 1 | package gasm.core.math.geom; 2 | typedef Box = { 3 | ?left:Float, 4 | ?right:Float, 5 | ?top:Float, 6 | ?bottom:Float 7 | } 8 | -------------------------------------------------------------------------------- /src/gasm/core/events/api/IEvent.hx: -------------------------------------------------------------------------------- 1 | package gasm.core.events.api; 2 | 3 | /** 4 | * @author Leo Bergman 5 | */ 6 | interface IEvent { 7 | var entity(default, null):Entity; 8 | } -------------------------------------------------------------------------------- /src/gasm/core/utils/texture/TextureData.hx: -------------------------------------------------------------------------------- 1 | package gasm.core.utils.texture; 2 | 3 | typedef TextureData = { 4 | frames:DynamicObject, 5 | meta:TextureMeta, 6 | } 7 | -------------------------------------------------------------------------------- /src/gasm/core/enums/Orientation.hx: -------------------------------------------------------------------------------- 1 | package gasm.core.enums; 2 | @:enum abstract Orientation(String) from String to String { 3 | var LANDSCAPE = 'landscape'; 4 | var PORTRAIT = 'portrait'; 5 | } -------------------------------------------------------------------------------- /tests.hxml: -------------------------------------------------------------------------------- 1 | -main TestMain 2 | -cp src 3 | -cp test 4 | -lib buddy:2.12.1 5 | -lib tweenx 6 | -lib travix:0.10.5 7 | -lib tink_core:1.27.1 8 | -dce no 9 | -D travis 10 | -D buddy-colors 11 | -------------------------------------------------------------------------------- /src/gasm/core/Context.hx: -------------------------------------------------------------------------------- 1 | package gasm.core; 2 | 3 | /** 4 | * ... 5 | * @author Leo Bergman 6 | */ 7 | interface Context { 8 | var baseEntity(get, null):Entity; 9 | var systems(default, null):Array; 10 | } -------------------------------------------------------------------------------- /src/gasm/core/utils/texture/SpritesheetFrame.hx: -------------------------------------------------------------------------------- 1 | package gasm.core.utils.texture; 2 | 3 | typedef SpritesheetFrame = { 4 | height:Int, 5 | offsetX:Int, 6 | offsetY:Int, 7 | width:Int, 8 | x:Int, 9 | y:Int, 10 | } 11 | -------------------------------------------------------------------------------- /src/gasm/core/enums/EventType.hx: -------------------------------------------------------------------------------- 1 | package gasm.core.enums; 2 | 3 | /** 4 | * @author Leo Bergman 5 | */ 6 | enum EventType { 7 | PRESS; 8 | DRAG; 9 | MOVE; 10 | OVER; 11 | OUT; 12 | DOWN; 13 | UP; 14 | RESIZE; 15 | } -------------------------------------------------------------------------------- /src/gasm/core/utils/texture/TextureMeta.hx: -------------------------------------------------------------------------------- 1 | package gasm.core.utils.texture; 2 | typedef TextureMeta = { 3 | app:String, 4 | format:String, 5 | image:String, 6 | scale:String, 7 | size:{ w:Int, h:Int }, 8 | smartupdate:String, 9 | version:String, 10 | } 11 | -------------------------------------------------------------------------------- /src/gasm/core/IEngine.hx: -------------------------------------------------------------------------------- 1 | package gasm.core; 2 | 3 | /** 4 | * @author Leo Bergman 5 | */ 6 | interface IEngine { 7 | public var baseEntity(default, null):Entity; 8 | public var getDelta:Null<() -> Float>; 9 | public function tick():Void; 10 | public function pause():Void; 11 | public function resume():Void; 12 | } 13 | -------------------------------------------------------------------------------- /src/gasm/core/utils/texture/TextureFrame.hx: -------------------------------------------------------------------------------- 1 | package gasm.core.utils.texture; 2 | 3 | typedef TextureFrame = { 4 | frame:{ x:Int, y:Int, w:Int, h:Int }, 5 | pivot:{ x:Float, y:Float }, 6 | rotated:Bool, 7 | sourceSize:{ w:Int, h:Int }, 8 | spriteSourceSize:{ x:Int, y:Int, w:Int, h:Int }, 9 | trimmed:Bool, 10 | } 11 | -------------------------------------------------------------------------------- /src/gasm/core/enums/SystemType.hx: -------------------------------------------------------------------------------- 1 | package gasm.core.enums; 2 | 3 | /** 4 | * Flags for different system types. 5 | * 6 | * @author Leo Bergman 7 | */ 8 | enum SystemType { 9 | // Systems will execute according to their order here. 10 | CORE; 11 | ACTOR; 12 | RENDERING; 13 | RENDERING3D; 14 | SOUND; 15 | } 16 | -------------------------------------------------------------------------------- /src/gasm/core/math/geom/Area.hx: -------------------------------------------------------------------------------- 1 | package gasm.core.math.geom; 2 | 3 | @:structInit 4 | class Area { 5 | public function new(x = 0, y = 0, w = 0, h = 0) { 6 | this.x = x; 7 | this.y = y; 8 | this.w = w; 9 | this.h = h; 10 | } 11 | 12 | public var x:Int = 0; 13 | public var y:Int = 0; 14 | public var w:Int = 0; 15 | public var h:Int = 0; 16 | } 17 | -------------------------------------------------------------------------------- /test/TestMain.hx: -------------------------------------------------------------------------------- 1 | import buddy.*; 2 | import gasm.core.EntityComponentTest; 3 | import gasm.core.EntityTest; 4 | import gasm.core.EngineTest; 5 | using buddy.Should; 6 | 7 | // Implement "Buddy" and define an array of classes within the brackets: 8 | class TestMain implements Buddy<[ 9 | EntityTest, 10 | EntityComponentTest, 11 | EngineTest 12 | ]> {} 13 | -------------------------------------------------------------------------------- /src/gasm/core/math/geom/Vector.hx: -------------------------------------------------------------------------------- 1 | package gasm.core.math.geom; 2 | 3 | @:structInit 4 | class Vector { 5 | public var x = 0.; 6 | public var y = 0.; 7 | public var z = 0.; 8 | 9 | public function new(x = 0., y = 0., z = 0.) { 10 | set(x, y, z); 11 | } 12 | 13 | public function set(x = 0., y = 0., z = 0.) { 14 | this.x = x; 15 | this.y = y; 16 | this.z = z; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/gasm/assets/FileEntry.hx: -------------------------------------------------------------------------------- 1 | package gasm.assets; 2 | 3 | typedef FileEntry = { 4 | name:String, 5 | size:Int, 6 | type:FileEntryType, 7 | path:String, 8 | ?children:Array, 9 | ?extension:String, 10 | ?extras:Array, 11 | } 12 | 13 | @:enum abstract FileEntryType(String) from String to String { 14 | var Directory = 'directory'; 15 | var File = 'file'; 16 | } 17 | -------------------------------------------------------------------------------- /src/gasm/core/math/geom/Rectangle.hx: -------------------------------------------------------------------------------- 1 | package gasm.core.math.geom; 2 | 3 | @:structInit 4 | class Rectangle { 5 | public function new(x = 0.0, y = 0.0, w = 0.0, h = 0.0) { 6 | this.x = x; 7 | this.y = y; 8 | this.w = w; 9 | this.h = h; 10 | } 11 | 12 | public var x:Float = 0; 13 | 14 | public var y:Float = 0; 15 | public var w:Float = 0; 16 | public var h:Float = 0; 17 | } 18 | -------------------------------------------------------------------------------- /src/gasm/core/ISystem.hx: -------------------------------------------------------------------------------- 1 | package gasm.core; 2 | import gasm.core.enums.ComponentType; 3 | import gasm.core.enums.SystemType; 4 | import haxe.EnumFlags; 5 | 6 | /** 7 | * @author Leo Bergman 8 | */ 9 | interface ISystem { 10 | var type(default, null):SystemType; 11 | var componentFlags(default, null):EnumFlags; 12 | function update(comp:Component, delta:Float):Void; 13 | } -------------------------------------------------------------------------------- /src/gasm/core/enums/ComponentType.hx: -------------------------------------------------------------------------------- 1 | package gasm.core.enums; 2 | 3 | /** 4 | * Flags for different component types. 5 | * 6 | * @author Leo Bergman 7 | */ 8 | enum ComponentType { 9 | Model; 10 | ActiveModel; 11 | SceneModel; 12 | GraphicsModel; 13 | Graphics3DModel; 14 | TextModel; 15 | SoundModel; 16 | Actor; 17 | Graphics; 18 | Graphics3D; 19 | Text; 20 | Sound; 21 | } 22 | -------------------------------------------------------------------------------- /src/gasm/core/events/ResizeEvent.hx: -------------------------------------------------------------------------------- 1 | package gasm.core.events; 2 | 3 | import gasm.core.events.api.IEvent; 4 | import gasm.core.math.geom.Point; 5 | 6 | class ResizeEvent implements IEvent { 7 | public var entity(default, null):Entity; 8 | public var size(default, null):Point; 9 | 10 | public function new(size:Point, entity:Entity) { 11 | this.entity = entity; 12 | this.size = size; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/gasm/core/System.hx: -------------------------------------------------------------------------------- 1 | package gasm.core; 2 | import gasm.core.enums.ComponentType; 3 | import gasm.core.enums.SystemType; 4 | import haxe.EnumFlags; 5 | 6 | /** 7 | * ... 8 | * @author Leo Bergman 9 | */ 10 | class System { 11 | public var type(default, null):SystemType; 12 | public var componentFlags(default, null):EnumFlags; 13 | 14 | public function new() { 15 | componentFlags = new EnumFlags(); 16 | } 17 | } -------------------------------------------------------------------------------- /src/gasm/core/events/InteractionEvent.hx: -------------------------------------------------------------------------------- 1 | package gasm.core.events; 2 | import gasm.core.events.api.IEvent; 3 | 4 | /** 5 | * ... 6 | * @author Leo Bergman 7 | */ 8 | class InteractionEvent implements IEvent { 9 | 10 | public var pos(default, null):{ x:Float, y:Float }; 11 | public var entity(default, null):Entity; 12 | 13 | public function new(pos:{x:Float, y:Float}, entity:Entity) { 14 | this.pos = pos; 15 | this.entity = entity; 16 | } 17 | 18 | } -------------------------------------------------------------------------------- /haxelib.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gasm", 3 | "license": "MIT", 4 | "tags": [ 5 | "ecs", 6 | "framework", 7 | "engine" 8 | ], 9 | "classPath": "src", 10 | "description": "Renderer agnostic ECS framework", 11 | "contributors": [ 12 | "leo.bergman" 13 | ], 14 | "releasenote": "See https://github.com/HacksawStudios/gasm/blob/master/doc/changes.md", 15 | "version": "2.1.0", 16 | "url": "https://github.com/HacksawStudios/gasm", 17 | "dependencies": { 18 | "tweenx": "" 19 | } 20 | } -------------------------------------------------------------------------------- /src/gasm/core/data/TextConfig.hx: -------------------------------------------------------------------------------- 1 | package gasm.core.data; 2 | 3 | typedef TextConfig = { 4 | ?text:String, 5 | font:Any, 6 | ?size:Null, 7 | ?color:Null, 8 | ?selectable:Bool, 9 | ?filters:Array, 10 | ?outlines:Array, 11 | ?backgroundColor:Int, 12 | ?width:Float, 13 | ?height:Float, 14 | ?autoSize:String, 15 | ?align:String, 16 | ?scaleToFit:Bool, 17 | ?cacheFiltersAsBitmap:Bool, 18 | ?letterSpacing:Int, 19 | ?lineSpacing:Int, 20 | ?rotation:Float, 21 | } 22 | -------------------------------------------------------------------------------- /src/gasm/core/utils/GeomUtils.hx: -------------------------------------------------------------------------------- 1 | package gasm.core.utils; 2 | 3 | import gasm.core.math.geom.Point; 4 | import gasm.core.math.geom.Rectangle; 5 | 6 | class GeomUtils { 7 | inline static public function hits(rect:Rectangle, point:Point):Bool { 8 | return point.x > rect.x && point.x < rect.x + rect.w && point.y > rect.y && point.y < rect.y + rect.h; 9 | } 10 | 11 | inline static public function addPoints(a:Point, b:Point):Point { 12 | return {x:a.x + b.x, y: a.y + b.y}; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/gasm/core/utils/Log.hx: -------------------------------------------------------------------------------- 1 | package gasm.core.utils; 2 | class Log { 3 | static public function log(?text:String, ?fields:Array) { 4 | trace("LOG:", text); 5 | } 6 | static public function info(?text:String, ?fields:Array) { 7 | trace("INFO:", text); 8 | } 9 | static public function warn(?text:String, ?fields:Array) { 10 | trace("WARN:", text); 11 | } 12 | static public function error(?text:String, ?fields:Array) { 13 | trace("ERROR:", text); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/gasm/extra/components/FPSDisplayComponent.hx: -------------------------------------------------------------------------------- 1 | package gasm.extra.components; 2 | import gasm.core.Component; 3 | import gasm.core.components.TextModelComponent; 4 | import gasm.core.enums.ComponentType; 5 | 6 | /** 7 | * ... 8 | * @author Leo Bergman 9 | */ 10 | class FPSDisplayComponent extends Component { 11 | 12 | public function new() { 13 | componentType = ComponentType.Actor; 14 | } 15 | 16 | override public function update(delta:Float) { 17 | var model = owner.get(TextModelComponent); 18 | var fps = owner.get(FPSComponent); 19 | model.text = Std.string(fps.fps); 20 | } 21 | 22 | } -------------------------------------------------------------------------------- /src/gasm/core/components/SoundModelComponent.hx: -------------------------------------------------------------------------------- 1 | package gasm.core.components; 2 | 3 | import gasm.core.Component; 4 | import gasm.core.enums.ComponentType; 5 | 6 | /** 7 | * Model to interface between different sound backends. 8 | * Automatically added when you add ComponentType.SOUND to an Entity. 9 | * 10 | * @author Leo Bergman 11 | */ 12 | class SoundModelComponent extends Component { 13 | public var volume(default, default):Float; 14 | public var pan(default, default):Float; 15 | public var pos(default, default):Float; 16 | public var playing(default, default):Bool; 17 | 18 | public function new() { 19 | componentType = ComponentType.SoundModel; 20 | } 21 | 22 | } -------------------------------------------------------------------------------- /src/jasper/Util.hx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013-2017, Nucleic Development Team. 3 | * Haxe Port Copyright (c) 2017 Jeremy Meltingtallow 4 | * 5 | * Distributed under the terms of the Modified BSD License. 6 | * 7 | * The full license is in the file COPYING.txt, distributed with this software. 8 | */ 9 | 10 | package jasper; 11 | 12 | class Util 13 | { 14 | private static inline var EPS = 1.0e-8; 15 | public static inline var FLOAT_MAX = 1.79769313486231e+308; 16 | 17 | /** 18 | * [Description] 19 | * @param value - 20 | * @return Bool 21 | */ 22 | public static function nearZero(value :Float) : Bool 23 | { 24 | return value < 0.0 ? -value < EPS : value < EPS; 25 | } 26 | } -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | dist: trusty 3 | 4 | language: haxe 5 | 6 | os: 7 | - linux 8 | # - osx 9 | 10 | haxe: 11 | - "3.4.4" 12 | - development 13 | 14 | matrix: 15 | allow_failures: 16 | - haxe: development 17 | 18 | install: 19 | - haxelib install travix 20 | - haxelib run travix install 21 | 22 | before_script: 23 | - phpenv config-rm xdebug.ini || true 24 | - sudo apt-get install default-jre 25 | 26 | script: 27 | 28 | - haxelib run travix interp 29 | - haxelib run travix neko 30 | - haxelib run travix python 31 | - haxelib run travix node 32 | #- haxelib run travix flash 33 | - haxelib run travix java 34 | - haxelib run travix cpp 35 | #- haxelib run travix cs 36 | - haxelib run travix php7 -------------------------------------------------------------------------------- /src/gasm/core/systems/ActorSystem.hx: -------------------------------------------------------------------------------- 1 | package gasm.core.systems; 2 | 3 | import gasm.core.ISystem; 4 | import gasm.core.System; 5 | import gasm.core.enums.ComponentType; 6 | import gasm.core.enums.SystemType; 7 | 8 | /** 9 | * Updates the actor components. 10 | * 11 | * @author Leo Bergman 12 | */ 13 | class ActorSystem extends System implements ISystem { 14 | public function new() { 15 | super(); 16 | type = SystemType.ACTOR; 17 | componentFlags.set(ComponentType.Actor); 18 | } 19 | 20 | inline public function update(comp:Component, delta:Float) { 21 | final inited = comp.inited; 22 | if (!inited) { 23 | comp.init(); 24 | comp.inited = true; 25 | } 26 | comp.update(delta); 27 | if (!inited) { 28 | comp.onAdded(); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/gasm/core/components/TextModelComponent.hx: -------------------------------------------------------------------------------- 1 | package gasm.core.components; 2 | 3 | /** 4 | * Model to interface between different text backends. 5 | * Automatically added when you add ComponentType.TEXT to an Entity. 6 | * 7 | * @author Leo Bergman 8 | */ 9 | class TextModelComponent extends SpriteModelComponent { 10 | public var text(default, default):String = ""; 11 | public var font(default, default):Null; 12 | public var size(default, default):Null; 13 | public var color(default, default):Null; 14 | public var selectable(default, default):Bool = false; 15 | 16 | public function new(text:String = "", size:Int = 14, col:UInt = 0xFFFFFF) { 17 | super(); 18 | this.text = text; 19 | this.size = size; 20 | this.color = col; 21 | } 22 | } -------------------------------------------------------------------------------- /src/jasper/Symbol.hx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013-2017, Nucleic Development Team. 3 | * Haxe Port Copyright (c) 2017 Jeremy Meltingtallow 4 | * 5 | * Distributed under the terms of the Modified BSD License. 6 | * 7 | * The full license is in the file COPYING.txt, distributed with this software. 8 | */ 9 | 10 | package jasper; 11 | 12 | class Symbol 13 | { 14 | public var m_type (default, null):SymbolType; 15 | 16 | public function new(type :SymbolType = SYM_INVALID) : Void 17 | { 18 | m_type = type; 19 | } 20 | 21 | private static inline var SYM_INVALID = INVALID; 22 | } 23 | 24 | @:enum 25 | @:notNull 26 | abstract SymbolType(Int) 27 | { 28 | var INVALID = 0; 29 | var EXTERNAL = 1; 30 | var SLACK = 2; 31 | var ERROR = 3; 32 | var DUMMY = 4; 33 | } -------------------------------------------------------------------------------- /src/gasm/system/components/PressSoundComponent.hx: -------------------------------------------------------------------------------- 1 | package gasm.system.components; 2 | import gasm.core.components.SoundModelComponent; 3 | import gasm.core.components.SpriteModelComponent; 4 | import gasm.core.Component; 5 | import gasm.core.enums.ComponentType; 6 | import gasm.core.enums.EventType; 7 | import gasm.core.events.InteractionEvent; 8 | 9 | /** 10 | * ... 11 | * @author Leo Bergman 12 | */ 13 | class PressSoundComponent extends Component { 14 | 15 | public function new() { 16 | componentType = ComponentType.Sound; 17 | } 18 | 19 | override public function init() { 20 | var spriteModel = owner.get(SpriteModelComponent); 21 | var soundModel = owner.get(SoundModelComponent); 22 | spriteModel.addHandler(EventType.PRESS, function(e:InteractionEvent) { 23 | soundModel.pos = 0; 24 | soundModel.playing = true; 25 | }); 26 | } 27 | } -------------------------------------------------------------------------------- /src/gasm/extra/components/FPSComponent.hx: -------------------------------------------------------------------------------- 1 | package gasm.extra.components; 2 | import haxe.Timer; 3 | import gasm.core.Component; 4 | import gasm.core.enums.ComponentType; 5 | 6 | /** 7 | * ... 8 | * @author Leo Bergman 9 | */ 10 | class FPSComponent extends Component { 11 | static var _cacheCount:Int = 0; 12 | static var _times:Array = []; 13 | 14 | public var fps(default, null):Int; 15 | 16 | public function new() { 17 | componentType = ComponentType.ActiveModel; 18 | } 19 | 20 | override public function update(delta:Float) { 21 | var currentTime = Timer.stamp(); 22 | _times.push(currentTime); 23 | while (_times[0] < currentTime - 1) { 24 | _times.shift(); 25 | } 26 | var currentCount = _times.length; 27 | _cacheCount = currentCount; 28 | fps = Math.round((currentCount + _cacheCount) / 2); 29 | } 30 | 31 | } -------------------------------------------------------------------------------- /src/gasm/core/utils/DynamicObject.hx: -------------------------------------------------------------------------------- 1 | package gasm.core.utils; 2 | 3 | abstract DynamicObject(Dynamic) from Dynamic { 4 | public inline function new() { 5 | this = {}; 6 | } 7 | 8 | @:arrayAccess 9 | public inline function set(key:String, value:T):Void { 10 | Reflect.setField(this, key, value); 11 | } 12 | 13 | @:arrayAccess 14 | public inline function get(key:String):Null { 15 | #if js 16 | return untyped this[key]; 17 | #else 18 | return Reflect.field(this, key); 19 | #end 20 | } 21 | 22 | public inline function exists(key:String):Bool { 23 | return Reflect.hasField(this, key); 24 | } 25 | 26 | public inline function remove(key:String):Bool { 27 | return Reflect.deleteField(this, key); 28 | } 29 | 30 | public inline function keys():Array { 31 | return Reflect.fields(this); 32 | } 33 | } -------------------------------------------------------------------------------- /src/gasm/system/components/PosFollowComponent.hx: -------------------------------------------------------------------------------- 1 | package gasm.system.components; 2 | 3 | import gasm.core.enums.ComponentType; 4 | import gasm.core.Entity; 5 | import gasm.core.components.SpriteModelComponent; 6 | import gasm.core.Component; 7 | 8 | class PosFollowComponent extends Component { 9 | 10 | var _model:SpriteModelComponent; 11 | var _followModel:SpriteModelComponent; 12 | var _followEntity:Entity; 13 | 14 | public function new(followEntity:Entity) { 15 | componentType = ComponentType.Actor; 16 | _followEntity = followEntity; 17 | } 18 | 19 | override public function init() { 20 | _model = owner.get(SpriteModelComponent); 21 | _followModel = _followEntity.get(SpriteModelComponent); 22 | super.init(); 23 | } 24 | 25 | override public function update(dt:Float) { 26 | _model.x = _followModel.x; 27 | _model.y = _followModel.y; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@hacksawstudios/gasm", 3 | "version": "2.2.0-rc.0", 4 | "description": "Entity Component System", 5 | "main": "index.js", 6 | "scripts": { 7 | "postinstall": "npx @hacksawstudios/haxe-module-installer", 8 | "pretest": "haxelib newrepo && haxelib --always install tests.hxml && haxelib run travix install", 9 | "test": "haxelib run travix node" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+ssh://git@gitlab.hacksawstudios.com/hacksawgaming/GASM.git" 14 | }, 15 | "author": "hacksawstudios", 16 | "license": "MIT", 17 | "bugs": { 18 | "url": "https://gitlab.hacksawstudios.com/hacksawgaming/GASM/issues" 19 | }, 20 | "homepage": "https://gitlab.hacksawstudios.com/hacksawgaming/GASM", 21 | "devDependencies": { 22 | "@hacksawstudios/gitlab-ci-releaser": "2.0.1", 23 | "fs-extra": "8.1.0" 24 | }, 25 | "dependencies": { 26 | "@hacksawstudios/haxe-module-installer": "1.2.1" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/gasm/core/systems/CoreSystem.hx: -------------------------------------------------------------------------------- 1 | package gasm.core.systems; 2 | 3 | import gasm.core.ISystem; 4 | import gasm.core.System; 5 | import gasm.core.enums.ComponentType; 6 | import gasm.core.enums.SystemType; 7 | 8 | /** 9 | * Updates core model compoenents. 10 | * 11 | * @author Leo Bergman 12 | */ 13 | class CoreSystem extends System implements ISystem { 14 | public function new() { 15 | super(); 16 | type = SystemType.CORE; 17 | componentFlags.set(ComponentType.GraphicsModel); 18 | componentFlags.set(ComponentType.Graphics3DModel); 19 | componentFlags.set(ComponentType.TextModel); 20 | componentFlags.set(ComponentType.ActiveModel); 21 | componentFlags.set(ComponentType.SoundModel); 22 | } 23 | 24 | inline public function update(comp:Component, delta:Float) { 25 | final inited = comp.inited; 26 | if (!inited) { 27 | comp.init(); 28 | comp.inited = true; 29 | } 30 | comp.update(delta); 31 | if (!inited) { 32 | comp.onAdded(); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/gasm/system/components/ScaleFollowComponent.hx: -------------------------------------------------------------------------------- 1 | package gasm.system.components; 2 | 3 | import gasm.core.components.SpriteModelComponent; 4 | import gasm.core.enums.ComponentType; 5 | import gasm.core.Entity; 6 | import gasm.core.Component; 7 | 8 | class ScaleFollowComponent extends Component { 9 | 10 | var _model:SpriteModelComponent; 11 | var _followModel:SpriteModelComponent; 12 | var _followEntity:Entity; 13 | var _multiplier:Float; 14 | 15 | public function new(followEntity:Entity, multiplier:Float = 1.0) { 16 | componentType = ComponentType.Actor; 17 | _followEntity = followEntity; 18 | _multiplier = multiplier; 19 | } 20 | 21 | override public function init() { 22 | _model = owner.get(SpriteModelComponent); 23 | _followModel = _followEntity.get(SpriteModelComponent); 24 | super.init(); 25 | } 26 | 27 | override public function update(dt:Float) { 28 | _model.xScale = _followModel.xScale * _multiplier; 29 | _model.yScale = _followModel.yScale * _multiplier; 30 | } 31 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Hacksaw Studios 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 | -------------------------------------------------------------------------------- /src/gasm/core/components/OffsetComponent.hx: -------------------------------------------------------------------------------- 1 | package gasm.core.components; 2 | 3 | import gasm.core.math.geom.Point; 4 | import gasm.core.Component; 5 | import gasm.core.components.AppModelComponent; 6 | import gasm.core.components.SpriteModelComponent; 7 | import gasm.core.enums.ComponentType; 8 | 9 | class OffsetComponent extends Component { 10 | public var offset(default, null):Point; 11 | 12 | var _appModel:AppModelComponent; 13 | var _scale:Float; 14 | 15 | public function new(?x:Float = 0, ?y:Float = 0, scale = 1.0) { 16 | offset = {x: x, y: y}; 17 | componentType = ComponentType.Actor; 18 | _scale = scale; 19 | } 20 | 21 | override public function init() { 22 | super.init(); 23 | _appModel = owner.getFromParents(AppModelComponent); 24 | } 25 | 26 | override public function update(dt:Float) { 27 | final model:SpriteModelComponent = owner.get(SpriteModelComponent); 28 | final offsetX = offset.x * _scale * _appModel.scale; 29 | final offsetY = offset.y * _scale * _appModel.scale; 30 | 31 | if (offsetX == model.offsetX && offsetY == model.offsetY) { 32 | return; 33 | } 34 | 35 | model.offsetX = offsetX; 36 | model.offsetY = offsetY; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/gasm/core/utils/SignalConnection.hx: -------------------------------------------------------------------------------- 1 | package gasm.core.utils; 2 | 3 | import gasm.core.api.IDisposable; 4 | 5 | /** 6 | * Represents a connected signal listener. 7 | */ 8 | class SignalConnection implements IDisposable { 9 | /** 10 | * True if the listener will remain connected after being used. 11 | */ 12 | public var stayInList (default, null):Bool; 13 | 14 | @:allow(gasm) function new(signal:SignalBase, listener:Dynamic) { 15 | _signal = signal; 16 | _listener = listener; 17 | stayInList = true; 18 | } 19 | 20 | /** 21 | * Tells the connection to dispose itself after being used once. 22 | * @returns This instance, for chaining. 23 | */ 24 | public function once() { 25 | stayInList = false; 26 | return this; 27 | } 28 | 29 | /** 30 | * Disconnects the listener from the signal. 31 | */ 32 | public function dispose() { 33 | if (_signal != null) { 34 | _signal.disconnect(this); 35 | _signal = null; 36 | } 37 | } 38 | 39 | @:allow(gasm) var _next:SignalConnection = null; 40 | 41 | @:allow(gasm) var _listener:Dynamic; 42 | private var _signal:SignalBase; 43 | } 44 | -------------------------------------------------------------------------------- /src/gasm/core/utils/Assert.hx: -------------------------------------------------------------------------------- 1 | package gasm.core.utils; 2 | 3 | using gasm.core.utils.StringUtils; 4 | 5 | class Assert { 6 | #if (debug) 7 | /** 8 | * Asserts that a condition is true. 9 | * @param message If this assertion fails, the message to include in the thrown error. 10 | * @param fields Optional fields to be formatted with the message, see Strings.withFields. 11 | */ 12 | public static function that(condition:Bool, ?message:String, ?fields:Array) { 13 | if (!condition) { 14 | fail(message, fields); 15 | } 16 | } 17 | 18 | /** 19 | * Immediately fails an assertion. Same as Assert.that(false). 20 | * @param message The message to include in the thrown error. 21 | * @param fields Optional fields to be formatted with the message, see Strings.withFields. 22 | */ 23 | public static function fail(?message:String, ?fields:Array) { 24 | var error = ''; 25 | if (message != null) { 26 | error = message; 27 | } 28 | if (fields != null) { 29 | error = error.withFields(fields); 30 | } 31 | throw error; 32 | } 33 | #else 34 | // In release builds, assertions are stripped out 35 | inline public static function that(condition:Bool, ?message:String, ?fields:Array) {} 36 | 37 | inline public static function fail(?message:String, ?fields:Array) {} 38 | #end 39 | } 40 | -------------------------------------------------------------------------------- /src/gasm/core/math/geom/Path.hx: -------------------------------------------------------------------------------- 1 | package gasm.core.math.geom; 2 | 3 | import h3d.Matrix; 4 | import h3d.Quat; 5 | import h3d.Vector; 6 | 7 | using Safety; 8 | 9 | /** 10 | Abstract over Arrays of vectors providing useful functions when working with connecting lines to form a path. 11 | **/ 12 | @:forward 13 | abstract Path(Array) from Array to Array { 14 | public function new(?points:Array) { 15 | this = points.or(new Array()); 16 | } 17 | 18 | /** 19 | Get direction and position of every mid-section of a path segment 20 | pointB......pointB 21 | @return Matrix for every midpoint 22 | **/ 23 | public function getMidpoints():Array { 24 | return [ 25 | for (i in 0...this.length - 1) { 26 | final pointA = this[i]; 27 | final pointB = this[i + 1]; 28 | // Calculate rotation by direction 29 | final dir = pointB.sub(pointA); 30 | dir.normalize(); 31 | final rotation = Matrix.lookAtX(dir); 32 | // Calculate position 33 | final position = new h3d.Vector(); 34 | position.x = pointA.x + (pointB.x - pointA.x) * 0.5; 35 | position.y = pointA.y + (pointB.y - pointA.y) * 0.5; 36 | position.z = pointA.z + (pointB.z - pointA.z) * 0.5; 37 | final m = new h3d.Matrix(); 38 | m.identity(); 39 | m.setPosition(position); 40 | m.multiply(rotation, m); 41 | m; 42 | } 43 | ]; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/gasm/core/components/FadeComponent.hx: -------------------------------------------------------------------------------- 1 | package gasm.core.components; 2 | 3 | import gasm.core.enums.ComponentType; 4 | 5 | class FadeComponent extends Component { 6 | var _config:FadeConfig; 7 | var _time:Float; 8 | var _model:SpriteModelComponent; 9 | var _model3D:ThreeDModelComponent; 10 | 11 | public function new(config:FadeConfig) { 12 | _config = config; 13 | componentType = ComponentType.Actor; 14 | } 15 | 16 | override public function init() { 17 | _model = owner.get(SpriteModelComponent); 18 | _model3D = owner.get(ThreeDModelComponent); 19 | _time = 0.0; 20 | } 21 | 22 | override public function update(dt:Float) { 23 | var rate = _time / _config.duration; 24 | _time += dt; 25 | if (rate <= 1) { 26 | var val = _config.update(rate); 27 | if (_model != null) { 28 | _model.alpha = val; 29 | } 30 | if (_model3D != null) { 31 | _model3D.alpha = val; 32 | } 33 | } else { 34 | var val = _config.update(1); 35 | if (_model != null) { 36 | _model.alpha = val; 37 | } 38 | if (_model3D != null) { 39 | _model3D.alpha = val; 40 | } 41 | if (_config.onComplete != null) { 42 | _config.onComplete(); 43 | } 44 | remove(); 45 | } 46 | 47 | super.update(dt); 48 | } 49 | } 50 | 51 | @:structInit 52 | class FadeConfig { 53 | public var duration:Float; 54 | public var update:(rate:Float) -> Float; 55 | public var onComplete:Null<() -> Void> = null; 56 | } 57 | -------------------------------------------------------------------------------- /src/jasper/Strength.hx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013-2017, Nucleic Development Team. 3 | * Haxe Port Copyright (c) 2017 Jeremy Meltingtallow 4 | * 5 | * Distributed under the terms of the Modified BSD License. 6 | * 7 | * The full license is in the file COPYING.txt, distributed with this software. 8 | */ 9 | 10 | package jasper; 11 | 12 | @:notNull 13 | abstract Strength(Float) to Float from Float 14 | { 15 | public inline function new(str :Float) : Void 16 | { 17 | this = str; 18 | } 19 | 20 | @:op(A < B) static function lt(a :Strength, b :Strength) : Bool; 21 | @:op(A > B) static function gt(a :Strength, b :Strength) : Bool; 22 | @:op(-A) function negate() : Strength; 23 | 24 | public static inline var REQUIRED :Strength = 1001001000; 25 | public static inline var STRONG :Strength = 1000000; 26 | public static inline var MEDIUM :Strength = 1000; 27 | public static inline var WEAK :Strength = 1; 28 | 29 | public static function create(a :Float, b :Float, c :Float, w :Float = 1.0) : Strength 30 | { 31 | var result = 0.0; 32 | result += Math.max( 0.0, Math.min( 1000.0, a * w ) ) * 1000000.0; 33 | result += Math.max( 0.0, Math.min( 1000.0, b * w ) ) * 1000.0; 34 | result += Math.max( 0.0, Math.min( 1000.0, c * w ) ); 35 | return new Strength(result); 36 | } 37 | 38 | public static function clip(value :Strength) :Strength 39 | { 40 | return new Strength(Math.max( 0.0, Math.min( REQUIRED, value ) )); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/gasm/core/utils/Signal0.hx: -------------------------------------------------------------------------------- 1 | package gasm.core.utils; 2 | /** 3 | * An alias for Signal0 listeners. 4 | */ 5 | typedef Listener0 = Void -> Void; 6 | 7 | /** 8 | * A zero-argument signal. See Signal1 and Signal2 for different arities. 9 | */ 10 | class Signal0 extends SignalBase { 11 | /** 12 | * @param listener An optional listener to immediately connect to the signal. 13 | */ 14 | public function new(?listener:Listener0) { 15 | super(listener); 16 | } 17 | 18 | /** 19 | * Connects a listener to this signal. 20 | * @param prioritize True if this listener should fire before others. 21 | * @returns A SignalConnection, that can be disposed to remove the listener. 22 | */ 23 | public function connect(listener:Listener0, prioritize:Bool = false):SignalConnection { 24 | return connectImpl(listener, prioritize); 25 | } 26 | 27 | /** 28 | * Emit the signal, notifying each connected listener. 29 | */ 30 | public function emit() { 31 | if (dispatching()) { 32 | defer(function() { 33 | emitImpl(); 34 | }); 35 | } else { 36 | emitImpl(); 37 | } 38 | } 39 | 40 | private function emitImpl() { 41 | var head = willEmit(); 42 | var p = head; 43 | while (p != null) { 44 | p._listener(); 45 | if (!p.stayInList) { 46 | p.dispose(); 47 | } 48 | p = p._next; 49 | } 50 | didEmit(head); 51 | } 52 | } -------------------------------------------------------------------------------- /src/gasm/extra/behaviors/BounceBehavior.hx: -------------------------------------------------------------------------------- 1 | package gasm.extra.behaviors; 2 | 3 | /** 4 | * ... 5 | * @author Leo Bergman 6 | */ 7 | @:final 8 | class BounceBehavior { 9 | var _gravity:Float = .5; 10 | var _minX:Float; 11 | var _minY:Float; 12 | var _maxX:Float; 13 | var _maxY:Float; 14 | var _x:Float; 15 | var _y:Float; 16 | var _speedX:Float; 17 | var _speedY:Float; 18 | 19 | inline public function new(?gravity:Float = 0.5, ?minX:Float = 0, ?minY:Float = 0, ?maxX:Float = 800, ?maxY:Float = 600, ?speed:Float = 5) { 20 | _gravity = gravity; 21 | _minX = minX; 22 | _minY = minY; 23 | _maxX = maxX; 24 | _maxY = maxY; 25 | _speedX = Math.random() * speed; 26 | _speedY = Math.random() * speed; 27 | } 28 | 29 | inline public function act(x:Float, y:Float):{x:Float, y:Float} { 30 | x += _speedX; 31 | y += _speedY; 32 | _speedY += _gravity; 33 | 34 | if (x > _maxX) { 35 | _speedX *= -1; 36 | x = _maxX; 37 | } 38 | else if (x < _minX) { 39 | _speedX *= -1; 40 | x = _minX; 41 | } 42 | 43 | if (y > _maxY) { 44 | _speedY *= -0.8; 45 | y = _maxY; 46 | if (Math.random() > 0.5) { 47 | _speedY -= 3 + Math.random() * 4; 48 | } 49 | } 50 | else if (y < _minY) { 51 | _speedY = 0; 52 | y = _minY; 53 | } 54 | return { x:x, y:y }; 55 | } 56 | 57 | } -------------------------------------------------------------------------------- /src/gasm/core/utils/Signal1.hx: -------------------------------------------------------------------------------- 1 | package gasm.core.utils; 2 | /** 3 | * An alias for Signal1 listeners. 4 | */ 5 | typedef Listener1 = A -> Void; 6 | 7 | /** 8 | * A one-argument signal. See Signal0 and Signal2 for different arities. 9 | */ 10 | class Signal1 extends SignalBase { 11 | /** 12 | * @param listener An optional listener to immediately connect to the signal. 13 | */ 14 | public function new(?listener:Listener1) { 15 | super(listener); 16 | } 17 | 18 | /** 19 | * Connects a listener to this signal. 20 | * @param prioritize True if this listener should fire before others. 21 | * @returns A SignalConnection, that can be disposed to remove the listener. 22 | */ 23 | public function connect(listener:Listener1, prioritize:Bool = false):SignalConnection { 24 | return connectImpl(listener, prioritize); 25 | } 26 | 27 | /** 28 | * Emit the signal, notifying each connected listener. 29 | */ 30 | public function emit(arg1:A) { 31 | if (dispatching()) { 32 | defer(function() { 33 | emitImpl(arg1); 34 | }); 35 | } else { 36 | emitImpl(arg1); 37 | } 38 | } 39 | 40 | private function emitImpl(arg1:A) { 41 | var head = willEmit(); 42 | var p = head; 43 | while (p != null) { 44 | p._listener(arg1); 45 | if (!p.stayInList) { 46 | p.dispose(); 47 | } 48 | p = p._next; 49 | } 50 | didEmit(head); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/gasm/core/utils/Signal2.hx: -------------------------------------------------------------------------------- 1 | package gasm.core.utils; 2 | /** 3 | * An alias for Signal2 listeners. 4 | */ 5 | typedef Listener2 = A -> B -> Void; 6 | 7 | /** 8 | * A two-argument signal. See Signal0 and Signal1 for different arities. 9 | */ 10 | class Signal2 extends SignalBase { 11 | /** 12 | * @param listener An optional listener to immediately connect to the signal. 13 | */ 14 | public function new(?listener:Listener2) { 15 | super(listener); 16 | } 17 | 18 | /** 19 | * Connects a listener to this signal. 20 | * @param prioritize True if this listener should fire before others. 21 | * @returns A SignalConnection, that can be disposed to remove the listener. 22 | */ 23 | public function connect(listener:Listener2, prioritize:Bool = false):SignalConnection { 24 | return connectImpl(listener, prioritize); 25 | } 26 | 27 | /** 28 | * Emit the signal, notifying each connected listener. 29 | */ 30 | public function emit(arg1:A, arg2:B) { 31 | if (dispatching()) { 32 | defer(function() { 33 | emitImpl(arg1, arg2); 34 | }); 35 | } else { 36 | emitImpl(arg1, arg2); 37 | } 38 | } 39 | 40 | private function emitImpl(arg1:A, arg2:B) { 41 | var head = willEmit(); 42 | var p = head; 43 | while (p != null) { 44 | p._listener(arg1, arg2); 45 | if (!p.stayInList) { 46 | p.dispose(); 47 | } 48 | p = p._next; 49 | } 50 | didEmit(head); 51 | } 52 | } -------------------------------------------------------------------------------- /src/jasper/Errors.hx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013-2017, Nucleic Development Team. 3 | * Haxe Port Copyright (c) 2017 Jeremy Meltingtallow 4 | * 5 | * Distributed under the terms of the Modified BSD License. 6 | * 7 | * The full license is in the file COPYING.txt, distributed with this software. 8 | */ 9 | 10 | package jasper; 11 | 12 | class UnsatisfiableConstraint 13 | { 14 | public inline function new(constraint :Constraint) : Void 15 | { 16 | trace('UnsatisfiableConstraint: $constraint'); 17 | } 18 | } 19 | 20 | class UnknownConstraint 21 | { 22 | public inline function new(constraint :Constraint) : Void 23 | { 24 | trace('UnknownConstraint: $constraint'); 25 | } 26 | } 27 | 28 | class DuplicateConstraint 29 | { 30 | public inline function new(constraint :Constraint) : Void 31 | { 32 | trace('DuplicateConstraint: $constraint'); 33 | } 34 | } 35 | 36 | class UnknownEditVariable 37 | { 38 | public inline function new(variable :Variable) : Void 39 | { 40 | trace('UnknownEditVariable: $variable'); 41 | } 42 | } 43 | 44 | class DuplicateEditVariable 45 | { 46 | public inline function new(variable :Variable) : Void 47 | { 48 | trace('DuplicateEditVariable: $variable'); 49 | } 50 | } 51 | 52 | class BadRequiredStrength 53 | { 54 | public inline function new() : Void 55 | { 56 | trace('BadRequiredStrength'); 57 | } 58 | } 59 | 60 | class InternalSolverError 61 | { 62 | public inline function new(message :String) : Void 63 | { 64 | trace('InternalSolverError: $message'); 65 | } 66 | } -------------------------------------------------------------------------------- /src/gasm/core/components/TweenComponent.hx: -------------------------------------------------------------------------------- 1 | package gasm.core.components; 2 | 3 | import gasm.core.Component; 4 | import gasm.core.components.SpriteModelComponent; 5 | import gasm.core.enums.ComponentType; 6 | import tweenx909.TweenX; 7 | import tweenxcore.Tools.Easing; 8 | 9 | class TweenComponent extends Component { 10 | var _properties:Dynamic; 11 | var _startProperties:Dynamic; 12 | var _duration:Float; 13 | var _spriteModel:SpriteModelComponent; 14 | var _easing:Float->Float; 15 | var _completeFunc:Void->Void; 16 | 17 | public function new(properties:Dynamic, duration:Float, startProperties:Dynamic = null, easing:Float->Float = null) { 18 | componentType = ComponentType.Actor; 19 | _properties = properties; 20 | _startProperties = startProperties; 21 | _duration = duration; 22 | _easing = easing == null ? Easing.linear : easing; 23 | } 24 | 25 | override public function init() { 26 | _spriteModel = owner.get(SpriteModelComponent); 27 | if (_startProperties != null) { 28 | for (field in Reflect.fields(_startProperties)) { 29 | Reflect.setField(_spriteModel, field, Reflect.field(_startProperties, field)); 30 | } 31 | } 32 | tween(); 33 | } 34 | 35 | public function onComplete(func:Void->Void) { 36 | _completeFunc = func; 37 | } 38 | 39 | inline function tween() { 40 | if (_spriteModel != null) { 41 | TweenX.to(_spriteModel, _properties, _duration, _easing).onFinish(() -> { 42 | if (_completeFunc != null) { 43 | _completeFunc(); 44 | } 45 | remove(); 46 | }); 47 | } else { 48 | trace("warn", 49 | "Attempting to tween entity without a sprite model. Ensure you have a component with ComponentType.GRAPHICS in the entity you like to tween."); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/gasm/core/macros/ComponentMacros.hx: -------------------------------------------------------------------------------- 1 | package gasm.core.macros; 2 | import haxe.macro.Expr; 3 | import haxe.macro.Context; 4 | import haxe.macro.Type; 5 | import haxe.macro.Tools; 6 | 7 | /** 8 | * ... 9 | * @author Leo Bergman 10 | */ 11 | class ComponentMacros { 12 | static var _index:Int = 0; 13 | static var _types:Map = new Map(); 14 | 15 | #if macro 16 | /** 17 | * Will add name getters and constants to Components to speed up resolution. 18 | */ 19 | public static function build():Array 20 | { 21 | var pos = Context.currentPos(); 22 | var classType:ClassType = Context.getLocalClass().get(); 23 | 24 | var name = Context.makeExpr(makeComponentName(classType), pos); 25 | 26 | var superClassType:ClassType; 27 | while (true) 28 | { 29 | superClassType = classType.superClass.t.get(); 30 | if (superClassType.meta.has(":componentAbstract") ) { 31 | break; 32 | } 33 | classType = superClassType; 34 | } 35 | var baseName = Context.makeExpr(makeComponentName(classType), pos); 36 | 37 | var c = macro : { 38 | override public function get_name() { 39 | return $name; 40 | } 41 | override public function get_baseName() { 42 | return $baseName; 43 | } 44 | public static var BASE_NAME:String = $baseName; 45 | }; 46 | 47 | switch (c) { 48 | case TAnonymous(fields): 49 | return Context.getBuildFields().concat(fields); 50 | default: 51 | throw 'unreachable'; 52 | } 53 | 54 | } 55 | 56 | static private function makeComponentName(classType:ClassType):String 57 | { 58 | var componentName = classType.pack.join(".") + "." + classType.name; 59 | return componentName; 60 | } 61 | #end 62 | } -------------------------------------------------------------------------------- /src/gasm/core/utils/texture/TexturePackerImport.hx: -------------------------------------------------------------------------------- 1 | package gasm.core.utils.texture; 2 | 3 | class TexturePackerImport { 4 | public static function parse(spriteJson:TextureData, behaviorJson:String = null, name:String = ""):ParseResult { 5 | // var bjson:Dynamic = Json.parse(behaviorJson); 6 | 7 | var frames:Array = []; 8 | //var behaviors = new StringMap(); 9 | // var behaviorFrames:Array = []; 10 | 11 | if (name == "") { 12 | name = spriteJson.meta.image; 13 | } 14 | 15 | var fields = Reflect.fields(spriteJson.frames); 16 | for (frame in fields) { 17 | var f = Reflect.field(spriteJson.frames, frame); 18 | var frameData:TextureFrame = f; 19 | frames.push({ 20 | x:frameData.frame.x, 21 | y:frameData.frame.y, 22 | width:frameData.frame.w, 23 | height:frameData.frame.h, 24 | offsetX:frameData.spriteSourceSize.x, 25 | offsetY:frameData.spriteSourceSize.y 26 | }); 27 | 28 | } 29 | /* 30 | var bfields = Reflect.fields(bjson); 31 | var count:Int = 0; 32 | for (prop in bfields) { 33 | var frame = Reflect.field(bjson, prop); 34 | var framelist:Array; 35 | framelist = frame.frames; 36 | 37 | var finalframelist:Array = new Array(); 38 | for (f in framelist) { 39 | finalframelist.push(f - 1); 40 | } 41 | 42 | var behaviorData:BehaviorData = new BehaviorData(prop, finalframelist, frame.loop, frame.speed, 0, 0); 43 | behaviors.set(prop, behaviorData); 44 | 45 | } 46 | */ 47 | return {frames:frames}; 48 | } 49 | } -------------------------------------------------------------------------------- /src/gasm/system/components/MouseFollowerComponent.hx: -------------------------------------------------------------------------------- 1 | package gasm.system.components; 2 | 3 | import gasm.core.math.geom.Point; 4 | import gasm.core.Component; 5 | import gasm.core.components.SpriteModelComponent; 6 | import gasm.core.enums.ComponentType; 7 | import gasm.core.math.geom.Coords; 8 | 9 | /** 10 | * ... 11 | * @author Leo Bergman 12 | */ 13 | class MouseFollowerComponent extends Component { 14 | var _oldPos:Point; 15 | var _anchorPosition:AnchorPosition; 16 | var _offset:Coords; 17 | var _model:SpriteModelComponent; 18 | 19 | public function new(anchorPosition:AnchorPosition = "center", ?offset:Coords) { 20 | _offset = (offset == null) ? {x:0, y:0} : offset; 21 | componentType = ComponentType.Actor; 22 | _oldPos = {x:0, y:0}; 23 | _anchorPosition = anchorPosition; 24 | } 25 | 26 | override public function init() { 27 | _model = owner.get(SpriteModelComponent); 28 | super.init(); 29 | } 30 | 31 | override public function update(dt:Float) { 32 | 33 | if(_oldPos.x == _model.stageMouseX && _oldPos.y == _model.stageMouseY) { 34 | return; 35 | } 36 | _oldPos.x = _model.stageMouseX; 37 | _oldPos.y = _model.stageMouseY; 38 | _model.x = _model.stageMouseX - (_model.width / 2) + _offset.x; 39 | _model.y = switch (_anchorPosition) { 40 | case AnchorPosition.TOP: _model.stageMouseY + _offset.y; 41 | case AnchorPosition.CENTER: _model.stageMouseY - (_model.height / 2) + _offset.y; 42 | case AnchorPosition.BOTTOM: _model.stageMouseY - _model.height + _offset.y; 43 | }; 44 | } 45 | } 46 | 47 | @:enum abstract AnchorPosition(String) from String to String { 48 | var TOP = 'top'; 49 | var CENTER = 'center'; 50 | var BOTTOM = 'bottom'; 51 | } -------------------------------------------------------------------------------- /src/gasm/core/components/AppModelComponent.hx: -------------------------------------------------------------------------------- 1 | package gasm.core.components; 2 | 3 | import gasm.core.api.singnals.TResize; 4 | import gasm.core.enums.ComponentType; 5 | import gasm.core.enums.Orientation; 6 | import gasm.core.math.geom.Point; 7 | import gasm.core.utils.Signal1; 8 | import haxe.ds.StringMap; 9 | 10 | using StringTools; 11 | using thx.Arrays; 12 | 13 | class AppModelComponent extends Component { 14 | /** 15 | * Device orientation. 16 | **/ 17 | public var orientation:Orientation; 18 | 19 | public var stageSize:Point = {x: 0, y: 0}; 20 | public var scale:Float = 1.0; 21 | public var resizeSignal:Signal1; 22 | public var stageMouseX:Float; 23 | public var stageMouseY:Float; 24 | public var frozen:Bool = true; 25 | public var freezeSignal:Signal1; 26 | public var customRenderCallback:Null<(engine:Any) -> Void> = null; 27 | public var pixelRatio = 1.0; 28 | public var assetsPath:String; 29 | public var disableShaderDevices = new StringMap>(); 30 | public var enableShaderDevices = new StringMap>(); 31 | 32 | public function new() { 33 | componentType = ComponentType.Model; 34 | resizeSignal = new Signal1(); 35 | freezeSignal = new Signal1(); 36 | } 37 | 38 | public function isShaderEnabled(name:String) { 39 | #if js 40 | final ua:String = js.Syntax.code('window.navigator.userAgent'); 41 | if (disableShaderDevices.exists(name) && disableShaderDevices.get(name) != null) { 42 | final disabled = disableShaderDevices.get(name).any(dev -> ua.contains(dev)); 43 | return !disabled; 44 | } 45 | 46 | if (enableShaderDevices.exists(name) && enableShaderDevices.get(name) != null) { 47 | final enabled = enableShaderDevices.get(name).any(dev -> ua.contains(dev)); 48 | return enabled; 49 | } 50 | #end 51 | return true; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/gasm/core/utils/MathUtils.hx: -------------------------------------------------------------------------------- 1 | package gasm.core.utils; 2 | 3 | class MathUtils { 4 | /** 5 | * Returns a string representation of the number in fixed-point notation. 6 | * Fixed-point notation means that the string will contain a specific number of digits 7 | * after the decimal point, as specified in the fractionDigits parameter. 8 | * The valid range for the fractionDigits parameter is from 0 to 20. 9 | * Specifying a value outside this range throws an exception. 10 | * @param fractionDigits An integer between 0 and 20, inclusive, that represents the desired number of decimal places. 11 | * @throws Throws an exception if the fractionDigits argument is outside the range 0 to 20. 12 | */ 13 | public static inline function toFixed(v:Float, fractionDigits:Int):String { 14 | #if (js || flash) 15 | return untyped v.toFixed(fractionDigits); 16 | #else 17 | if(fractionDigits < 0 || fractionDigits > 20) throw 'toFixed have a range of 0 to 20. Specified value is not within expected range.'; 18 | var b = Math.pow(10, fractionDigits); 19 | var s = Std.string(v); 20 | var dotIndex = s.indexOf('.'); 21 | if(dotIndex >= 0) { 22 | var diff = fractionDigits - (s.length - (dotIndex + 1)); 23 | if(diff > 0) { 24 | s = StringTools.rpad(s, "0", s.length + diff); 25 | } else { 26 | s = Std.string(Math.round(v * b) / b); 27 | } 28 | } else { 29 | s += "."; 30 | s = StringTools.rpad(s, "0", s.length + fractionDigits); 31 | } 32 | return s; 33 | #end 34 | } 35 | 36 | /** 37 | * Returns radians from degree 38 | **/ 39 | public static inline function degToRad(deg:Float):Float { 40 | return (Math.PI / 180) * deg; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/gasm/extra/components/BounceAroundComponent.hx: -------------------------------------------------------------------------------- 1 | package gasm.extra.components; 2 | 3 | import gasm.core.Component; 4 | import gasm.core.components.SpriteModelComponent; 5 | import gasm.core.enums.ComponentType; 6 | 7 | /** 8 | * ... 9 | * @author Leo Bergman 10 | */ 11 | class BounceAroundComponent extends Component { 12 | var _gravity:Float; 13 | var _minX:Float; 14 | var _minY:Float; 15 | var _maxX:Float; 16 | var _maxY:Float; 17 | 18 | public function new(gravity:Float = 0.5, minX:Float = 0, minY:Float = 0, maxX:Float = 800, maxY:Float = 600) { 19 | componentType = ComponentType.Actor; 20 | _gravity = gravity; 21 | _minX = minX; 22 | _minY = minY; 23 | _maxX = maxX; 24 | _maxY = maxY; 25 | } 26 | 27 | override public function init() { 28 | var model = owner.get(SpriteModelComponent); 29 | model.x = 0; 30 | model.y = 0; 31 | model.speedX = Math.random() * 5; 32 | model.speedY = (Math.random() * 5); 33 | } 34 | 35 | override public function update(delta:Float) { 36 | var model:SpriteModelComponent = owner.get(SpriteModelComponent); 37 | model.x += model.speedX; 38 | model.y += model.speedY; 39 | model.speedY += _gravity; 40 | 41 | if (model.x > _maxX) { 42 | model.speedX *= -1; 43 | model.x = _maxX; 44 | } 45 | else if (model.x < _minX) { 46 | model.speedX *= -1; 47 | model.x = _minX; 48 | } 49 | 50 | if (model.y > _maxY) { 51 | model.speedY *= -0.8; 52 | model.y = _maxY; 53 | if (Math.random() > 0.5) { 54 | model.speedY -= 3 + Math.random() * 4; 55 | } 56 | } 57 | else if (model.y < _minY) { 58 | model.speedY = 0; 59 | model.y = _minY; 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /GASM.hxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | "$(CompilerPath)/haxelib" run munit test -browser Firefox 44 | "$(CompilerPath)/haxelib" run lime build "$(OutputFile)" $(TargetBuild) -$(BuildConfig) 45 | 46 | 47 | 48 | 49 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /src/jasper/Value.hx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013-2017, Nucleic Development Team. 3 | * Haxe Port Copyright (c) 2017 Jeremy Meltingtallow 4 | * 5 | * Distributed under the terms of the Modified BSD License. 6 | * 7 | * The full license is in the file COPYING.txt, distributed with this software. 8 | */ 9 | 10 | package jasper; 11 | 12 | @:notNull 13 | abstract Value(Float) to Float from Float 14 | { 15 | @:op(A*B) static function multiply(a:Value,B:Value):Value; 16 | @:op(A/B) static function divide(a:Value,B:Value):Value; 17 | @:op(A+B) static function add(a:Value,B:Value):Value; 18 | @:op(-A) static function negate(a:Value):Value; 19 | 20 | @:op(A-B) static function subtractExpression(constant :Value, expression :Expression) : Expression 21 | { 22 | return -expression + constant; 23 | } 24 | 25 | @:op(A-B) static function subtractTerm(constant :Value, term :Term) : Expression 26 | { 27 | return -term + constant; 28 | } 29 | 30 | @:op(A-B) static function subtractVariable(constant :Value, variable :Variable) : Expression 31 | { 32 | return -variable + constant; 33 | } 34 | 35 | @:op(A<=B) static function lteExpression(constant :Value, expression :Expression) : Constraint 36 | { 37 | return expression >= constant; 38 | } 39 | 40 | @:op(A<=B) static function lteTerm(constant :Value, term :Term) : Constraint 41 | { 42 | return term >= constant; 43 | } 44 | 45 | @:op(A<=B) static function lteVariable(constant :Value, variable :Variable) : Constraint 46 | { 47 | return variable >= constant; 48 | } 49 | 50 | @:op(A>=B) static function gteExpression(constant :Value, expression :Expression) : Constraint 51 | { 52 | return expression <= constant; 53 | } 54 | 55 | @:op(A>=B) static function gteTerm(constant :Value, term :Term) : Constraint 56 | { 57 | return term <= constant; 58 | } 59 | 60 | @:op(A>=B) static function gteVariable(constant :Value, variable :Variable) : Constraint 61 | { 62 | return variable <= constant; 63 | } 64 | } -------------------------------------------------------------------------------- /src/gasm/core/Engine.hx: -------------------------------------------------------------------------------- 1 | package gasm.core; 2 | 3 | import haxe.EnumFlags; 4 | import haxe.Timer; 5 | import gasm.core.systems.ActorSystem; 6 | import gasm.core.systems.CoreSystem; 7 | import gasm.core.enums.ComponentType; 8 | import gasm.core.enums.SystemType; 9 | import gasm.core.ISystem; 10 | 11 | /** 12 | * ... 13 | * @author Leo Bergman 14 | */ 15 | class Engine implements IEngine { 16 | public var baseEntity(default, null):Entity; 17 | public var getDelta:Null<() -> Float> = null; 18 | 19 | var _systems:Array; 20 | var _lastTime:Float = 0; 21 | var _paused:Bool; 22 | 23 | public function new(systems:Array) { 24 | systems.push(new CoreSystem()); 25 | systems.push(new ActorSystem()); 26 | systems.sort(function(x, y) { 27 | var xval = new EnumFlags(); 28 | xval.set(x.type); 29 | var yval = new EnumFlags(); 30 | yval.set(y.type); 31 | if (xval.toInt() > yval.toInt()) { 32 | return 1; 33 | } 34 | if (xval.toInt() < yval.toInt()) { 35 | return -1; 36 | } 37 | return 0; 38 | }); 39 | _systems = systems; 40 | _lastTime = Timer.stamp(); 41 | baseEntity = new Entity("base"); 42 | } 43 | 44 | public function tick() { 45 | if (!_paused) { 46 | var delta = 0.0; 47 | if (getDelta != null) { 48 | delta = getDelta(); 49 | } else { 50 | var now = Timer.stamp(); 51 | delta = now - _lastTime; 52 | _lastTime = now; 53 | } 54 | updateEntity(baseEntity, delta); 55 | } 56 | } 57 | 58 | public function pause() { 59 | _paused = true; 60 | } 61 | 62 | public function resume() { 63 | _lastTime = Timer.stamp(); 64 | _paused = false; 65 | } 66 | 67 | function updateEntity(entity:Entity, delta:Float) { 68 | for (i in 0..._systems.length) { 69 | var comp = entity.firstComponent; 70 | var system = _systems[i]; 71 | while (comp != null) { 72 | var next = comp.next; 73 | if (system.componentFlags.has(comp.componentType)) { 74 | system.update(comp, delta); 75 | } 76 | comp = next; 77 | } 78 | } 79 | var ent = entity.firstChild; 80 | while (ent != null) { 81 | var next = ent.next; 82 | updateEntity(ent, delta); 83 | ent = next; 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /test/gasm/core/utils/texture/TexturePackerImportTest.hx: -------------------------------------------------------------------------------- 1 | package gasm.core.utils.texture; 2 | 3 | import buddy.BuddySuite; 4 | 5 | using buddy.Should; 6 | 7 | class TexturePackerImportTest extends BuddySuite { 8 | public function new() { 9 | describe("parse", { 10 | it("should return a valid spritesheet if provided valid json", { 11 | var sheetJson:TextureData = { 12 | frames: { 13 | "0.png": { 14 | "frame": { 15 | "x": 1, 16 | "y": 1, 17 | "w": 100, 18 | "h": 140 19 | }, 20 | "rotated": false, 21 | "trimmed": false, 22 | "spriteSourceSize": { 23 | "x": 0, 24 | "y": 0, 25 | "w": 100, 26 | "h": 140 27 | }, 28 | "sourceSize": { 29 | "w": 100, 30 | "h": 140 31 | }, 32 | "pivot": { 33 | "x": 0.5, 34 | "y": 0.5 35 | } 36 | } 37 | }, 38 | meta: { 39 | "app": "http://www.codeandweb.com/texturepacker", 40 | "version": "1.0", 41 | "image": "CARDS.png", 42 | "format": "RGBA8888", 43 | "size": { 44 | "w": 1814, 45 | "h": 426 46 | }, 47 | "scale": "1", 48 | "smartupdate": "$TexturePacker:SmartUpdate:f7228e659f09a1e1f4302b25a33f1608:56bbdd1249a1a5cc8691dca2dd61d6c6:0c259760b19e457668caa09afd5c317d$" 49 | } 50 | } 51 | var result = TexturePackerImport.parse(sheetJson); 52 | result.frames[0].height.should.be(140); 53 | }); 54 | }); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | image: hacksawgaming/hacksaw-build:4.2.1-1 2 | 3 | stages: 4 | - test 5 | - release 6 | - mergeback 7 | 8 | test: 9 | stage: test 10 | before_script: 11 | - yarn 12 | script: 13 | - npm test 14 | except: 15 | refs: 16 | - tags 17 | variables: 18 | - $CI_COMMIT_MESSAGE =~ /^\d+\.\d+\.\d+/ 19 | - $CI_COMMIT_MESSAGE =~ /^Update (package from master|version)$/m 20 | 21 | package_release: 22 | stage: release 23 | before_script: 24 | - yarn 25 | - git config --global user.email "ci@hacksawstudios.com" 26 | - git config --global user.name "CI" 27 | script: 28 | - npx @hacksawstudios/gitlab-ci-releaser --npm 29 | - yarn 30 | - git add . 31 | - git commit -m "Update version" 32 | - git push --follow-tags "https://${GITLAB_USER_NAME}:${GITLAB_CI_RELEASER_TOKEN}@${CI_REPOSITORY_URL#*@}" "HEAD:master" 33 | only: 34 | refs: 35 | - master 36 | - /^patch_.*$/ 37 | except: 38 | variables: 39 | - $CI_COMMIT_MESSAGE =~ /^\d+\.\d+\.\d+$/m 40 | - $CI_COMMIT_MESSAGE =~ /^Update (package from master|version)$/m 41 | 42 | package_rc: 43 | stage: release 44 | before_script: 45 | - yarn 46 | - git config --global user.email "ci@hacksawstudios.com" 47 | - git config --global user.name "CI" 48 | script: 49 | - npx @hacksawstudios/gitlab-ci-releaser --npm --preid rc 50 | - yarn 51 | - git add . 52 | - git commit -m "Update version" 53 | - git push "https://${GITLAB_USER_NAME}:${GITLAB_CI_RELEASER_TOKEN}@${CI_REPOSITORY_URL#*@}" "HEAD:develop" 54 | only: 55 | - develop 56 | except: 57 | variables: 58 | - $CI_COMMIT_MESSAGE =~ /^\d+\.\d+\.\d+(?:-rc\.[0-9]+)?/ 59 | - $CI_COMMIT_MESSAGE =~ /^Update (package from master|version)$/m 60 | 61 | merge_back: 62 | stage: mergeback 63 | before_script: 64 | - yarn 65 | - git config --global user.email "ci@hacksawstudios.com" 66 | - git config --global user.name "CI" 67 | script: 68 | - git add . 69 | - git commit -m "Update version" 70 | - git fetch 71 | - git checkout origin/develop 72 | - git merge origin/master -X theirs 73 | - npm version preminor --preid=rc -m "Update package from master" 74 | - git push "https://${GITLAB_USER_NAME}:${GITLAB_CI_RELEASER_TOKEN}@${CI_REPOSITORY_URL#*@}" "HEAD:develop" 75 | only: 76 | refs: 77 | - tags 78 | variables: 79 | - $CI_COMMIT_MESSAGE =~ /^\d+\.\d+\.\d+$/m -------------------------------------------------------------------------------- /src/jasper/Constraint.hx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013-2017, Nucleic Development Team. 3 | * Haxe Port Copyright (c) 2017 Jeremy Meltingtallow 4 | * 5 | * Distributed under the terms of the Modified BSD License. 6 | * 7 | * The full license is in the file COPYING.txt, distributed with this software. 8 | */ 9 | 10 | package jasper; 11 | 12 | class _Constraint_ 13 | { 14 | public var m_expression (default, null):Expression; 15 | public var m_strength (default, null):Strength; 16 | public var m_op (default, null):RelationalOperator; 17 | 18 | /** 19 | * [Description] 20 | * @param expr - 21 | * @param op - 22 | * @param strength - 23 | */ 24 | private function new(expr :Expression, op :RelationalOperator, strength :Strength) : Void 25 | { 26 | this.m_expression = expr; 27 | this.m_op = op; 28 | this.m_strength = strength; 29 | } 30 | 31 | /** 32 | * [Description] 33 | * @param other - 34 | * @param strength - 35 | * @return Constraint 36 | */ 37 | public static function fromConstraint(other :Constraint, strength :Strength) : Constraint 38 | { 39 | strength = Strength.clip(strength); 40 | return new _Constraint_(other.m_expression,other.m_op,strength); 41 | } 42 | 43 | /** 44 | * [Description] 45 | * @param expr - 46 | * @param op - 47 | * @param strength - 48 | */ 49 | public static function fromExpression(expr :Expression, op :RelationalOperator, strength :Strength) : Constraint 50 | { 51 | expr = reduce(expr); 52 | strength = Strength.clip(strength); 53 | return new _Constraint_(expr,op,strength); 54 | } 55 | 56 | /** 57 | * [Description] 58 | * @param expr - 59 | * @return Expression 60 | */ 61 | private static function reduce(expr :Expression) :Expression 62 | { 63 | var vars = new Map(); 64 | 65 | for(term in expr.m_terms) { 66 | if(!vars.exists(term.m_variable)) { 67 | vars[term.m_variable] = 0; 68 | } 69 | vars[term.m_variable] += term.m_coefficient; 70 | } 71 | 72 | var terms = [for (key in vars.keys()) new Term(key, vars.exists(key) ? vars.get(key) : 0)]; 73 | 74 | return new Expression(terms, expr.m_constant); 75 | } 76 | 77 | /** 78 | * [Description] 79 | * @return String 80 | */ 81 | public function toString() : String 82 | { 83 | return "expression: (" + m_expression + ") strength: " + m_strength + " operator: " + m_op; 84 | } 85 | } 86 | 87 | enum RelationalOperator 88 | { 89 | OP_LE; 90 | OP_GE; 91 | OP_EQ; 92 | } 93 | 94 | @:forward 95 | @:forwardStatics 96 | @:notNull 97 | abstract Constraint(_Constraint_) to _Constraint_ from _Constraint_ 98 | { 99 | public function new(expr :Expression, op :RelationalOperator, strength :Strength = Strength.REQUIRED) : Void 100 | { 101 | this = _Constraint_.fromExpression(expr, op, strength); 102 | } 103 | 104 | @:op(A|B) @:commutative static function modifyStrength(constraint :Constraint, strength :Strength) : Constraint 105 | { 106 | return _Constraint_.fromConstraint(constraint, strength); 107 | } 108 | } -------------------------------------------------------------------------------- /src/gasm/core/components/SceneModelComponent.hx: -------------------------------------------------------------------------------- 1 | package gasm.core.components; 2 | 3 | import gasm.core.Component; 4 | import gasm.core.enums.ComponentType; 5 | import haxe.ds.StringMap; 6 | 7 | using Lambda; 8 | 9 | class SceneModelComponent extends Component { 10 | public var scenes:Array = []; 11 | public var dirty(default, null) = false; 12 | public var sceneMap = new StringMap(); 13 | 14 | public function new() { 15 | componentType = ComponentType.SceneModel; 16 | } 17 | 18 | public function addScene(scene:SceneLayer):Entity { 19 | removeScene(scene.name); 20 | scene.entity = new Entity(scene.name); 21 | if (scene.layerIndex == null) { 22 | scene.layerIndex = scenes.length; 23 | } 24 | scenes.push(scene); 25 | sortScenes(); 26 | addEntity(scene, owner); 27 | dirty = true; 28 | return scene.entity; 29 | } 30 | 31 | /** 32 | * Make scene invisible 33 | * @param name Name of scene to make invisible 34 | */ 35 | public function hideScene(name:String) { 36 | // Override in integration 37 | } 38 | 39 | /** 40 | * Make scene visible 41 | * @param name Name of scene to make visible 42 | */ 43 | public function showScene(name:String) { 44 | // Override in integration 45 | } 46 | 47 | /** 48 | * Disable interactivity for a scene 49 | * @param name Name of scene to disable 50 | */ 51 | public function disableScene(name:String) { 52 | // Override in integration 53 | } 54 | 55 | /** 56 | * Enable interactivity for a scene 57 | * @param name Name of scene to disable 58 | */ 59 | public function enableScene(name:String) { 60 | // Override in integration 61 | } 62 | 63 | public function removeScene(name:String) { 64 | final scene = scenes.find(s -> s.name == name); 65 | if (scene != null) { 66 | final s2d:h2d.Scene = scene.instance; 67 | if (s2d != null) { 68 | s2d.removeChildren(); 69 | s2d.remove(); 70 | s2d.dispose(); 71 | } 72 | final s3d:h3d.scene.Scene = scene.instance; 73 | if (s3d != null) { 74 | s3d.removeChildren(); 75 | s3d.remove(); 76 | s3d.dispose(); 77 | } 78 | if (scene.entity.parent != null) { 79 | scene.entity.parent.removeChild(scene.entity); 80 | } 81 | scene.entity.dispose(); 82 | dirty = true; 83 | scenes.remove(scene); 84 | sceneMap.remove(scene.name); 85 | } 86 | } 87 | 88 | public function moveToTop(name:String) { 89 | final scene = scenes.find(val -> name == val.name); 90 | if (scene != null) { 91 | scene.layerIndex = scenes.length; 92 | sortScenes(); 93 | } 94 | } 95 | 96 | public function moveToBottom(name:String) { 97 | final scene = scenes.find(val -> name == val.name); 98 | if (scene != null) { 99 | scene.layerIndex = -1; 100 | sortScenes(); 101 | } 102 | } 103 | 104 | public function swapDepths(nameA:String, nameB:String) { 105 | final sceneA = scenes.find(val -> nameA == val.name); 106 | final sceneB = scenes.find(val -> nameB == val.name); 107 | final layerIndexA = sceneA.layerIndex; 108 | final layerIndexB = sceneB.layerIndex; 109 | sceneA.layerIndex = layerIndexB; 110 | sceneB.layerIndex = layerIndexA; 111 | sortScenes(); 112 | } 113 | 114 | function addEntity(scene:SceneLayer, baseEntity:Entity):Entity { 115 | // Override in integration 116 | return null; 117 | } 118 | 119 | function sortScenes() { 120 | scenes.sort((a, b) -> a.layerIndex > b.layerIndex ? 1 : -1); 121 | var i = 0; 122 | scenes.map(s -> { 123 | s.layerIndex = i++; 124 | return s; 125 | }); 126 | } 127 | } 128 | 129 | @:structInit 130 | class SceneLayer { 131 | public var name:String; 132 | public var is3D = false; 133 | public var layerIndex:Null = null; 134 | public var interactive = false; 135 | public var entity:Entity = null; 136 | public var instance:Any = null; 137 | } 138 | -------------------------------------------------------------------------------- /src/gasm/core/utils/SignalBase.hx: -------------------------------------------------------------------------------- 1 | package gasm.core.utils; 2 | class SignalBase { 3 | private function new(listener:Dynamic) { 4 | _head = (listener != null) ? new SignalConnection(this, listener) : null; 5 | _deferredTasks = null; 6 | } 7 | 8 | /** 9 | * Whether this signal has at least one listener. 10 | */ 11 | inline public function hasListeners():Bool { 12 | return _head != null; 13 | } 14 | 15 | private function connectImpl(listener:Dynamic, prioritize:Bool):SignalConnection { 16 | var conn = new SignalConnection(this, listener); 17 | if (dispatching()) { 18 | defer(function() { 19 | listAdd(conn, prioritize); 20 | }); 21 | } else { 22 | listAdd(conn, prioritize); 23 | } 24 | return conn; 25 | } 26 | 27 | @:allow(gasm) function disconnect(conn:SignalConnection) { 28 | if (dispatching()) { 29 | defer(function() { 30 | listRemove(conn); 31 | }); 32 | } else { 33 | listRemove(conn); 34 | } 35 | } 36 | 37 | private function defer(fn:Void -> Void) { 38 | var tail = null, p = _deferredTasks; 39 | while (p != null) { 40 | tail = p; 41 | p = p.next; 42 | } 43 | 44 | var task = new Task(fn); 45 | if (tail != null) { 46 | tail.next = task; 47 | } else { 48 | _deferredTasks = task; 49 | } 50 | } 51 | 52 | private function willEmit():SignalConnection { 53 | // Should never happen, since the public emit methods will defer, but just in case... 54 | Assert.that(!dispatching()); 55 | 56 | var snapshot = _head; 57 | _head = DISPATCHING_SENTINEL; 58 | return snapshot; 59 | } 60 | 61 | private function didEmit(head:SignalConnection) { 62 | _head = head; 63 | 64 | var snapshot = _deferredTasks; 65 | _deferredTasks = null; 66 | while (snapshot != null) { 67 | snapshot.fn(); 68 | snapshot = snapshot.next; 69 | } 70 | } 71 | 72 | private function listAdd(conn:SignalConnection, prioritize:Bool) { 73 | if (prioritize) { 74 | // Prepend it to the beginning of the list 75 | conn._next = _head; 76 | _head = conn; 77 | } else { 78 | // Append it to the end of the list 79 | var tail = null, p = _head; 80 | while (p != null) { 81 | tail = p; 82 | p = p._next; 83 | } 84 | if (tail != null) { 85 | tail._next = conn; 86 | } else { 87 | _head = conn; 88 | } 89 | } 90 | } 91 | 92 | private function listRemove(conn:SignalConnection) { 93 | var prev:SignalConnection = null, p = _head; 94 | while (p != null) { 95 | if (p == conn) { 96 | // Splice out p 97 | var next = p._next; 98 | if (prev == null) { 99 | _head = next; 100 | } else { 101 | prev._next = next; 102 | } 103 | return; 104 | } 105 | prev = p; 106 | p = p._next; 107 | } 108 | } 109 | 110 | inline private function dispatching():Bool { 111 | return _head == DISPATCHING_SENTINEL; 112 | } 113 | 114 | private static var DISPATCHING_SENTINEL = new SignalConnection(null, null); 115 | 116 | private var _head:SignalConnection; 117 | private var _deferredTasks:Task; 118 | } 119 | 120 | private class Task { 121 | public var fn:Void -> Void; 122 | public var next:Task = null; 123 | 124 | public function new(fn:Void -> Void) { 125 | this.fn = fn; 126 | } 127 | } -------------------------------------------------------------------------------- /src/gasm/core/utils/StringUtils.hx: -------------------------------------------------------------------------------- 1 | package gasm.core.utils; 2 | 3 | using StringTools; 4 | 5 | /** 6 | * Utility mixins for Strings. Designed to be imported with 'using'. 7 | */ 8 | class StringUtils { 9 | /** 10 | * Gets the extension of a file name, or null if there is no extension. 11 | */ 12 | public static function getFileExtension(fileName:String):String { 13 | var dot = fileName.lastIndexOf("."); 14 | return (dot > 0) ? fileName.substr(dot + 1) : null; 15 | } 16 | 17 | /** 18 | * Returns a file name without its extension. 19 | */ 20 | public static function removeFileExtension(fileName:String):String { 21 | var dot = fileName.lastIndexOf("."); 22 | return (dot > 0) ? fileName.substr(0, dot) : fileName; 23 | } 24 | 25 | /** 26 | * Gets the extension of a full path or URL, with special handling for '/' and '?' characters. 27 | * Returns null if there is no extension. 28 | */ 29 | public static function getUrlExtension(url:String):String { 30 | var question = url.lastIndexOf("?"); 31 | if (question >= 0) { 32 | url = url.substr(0, question); 33 | } 34 | var slash = url.lastIndexOf("/"); 35 | if (slash >= 0) { 36 | url = url.substr(slash + 1); 37 | } 38 | return getFileExtension(url); 39 | } 40 | 41 | /** 42 | * Joins two strings with a path separator. 43 | */ 44 | public static function joinPath(base:String, relative:String):String { 45 | if (base.length > 0 && base.fastCodeAt(base.length - 1) != "/".code) { 46 | base += "/"; // Ensure base ends with a trailing slash 47 | } 48 | return base + relative; 49 | } 50 | 51 | public static function hashCode(str:String):Int { 52 | var code = 0; 53 | if (str != null) { 54 | for (ii in 0...str.length) { 55 | code = Std.int(31 * code + str.fastCodeAt(ii)); 56 | } 57 | } 58 | return code; 59 | } 60 | 61 | /** 62 | * Substitute all "{n}" tokens with the corresponding values. 63 | * 64 | * ```haxe 65 | * "{1} sat on a {0}".substitute(["wall", "Humpty Dumpty"]); 66 | * // returns "Humpty Dumpty sat on a wall" 67 | * ``` 68 | */ 69 | public static function substitute(str:String, values:Array):String { 70 | // FIXME(bruno): If your {0} replacement has a {1} in it, then that'll get replaced next 71 | // iteration 72 | for (ii in 0...values.length) { 73 | str = str.replace("{" + ii + "}", values[ii]); 74 | } 75 | return str; 76 | } 77 | 78 | /** 79 | * Format a message with named parameters into a standard format for logging and errors. 80 | * 81 | * ```haxe 82 | * "Wobbles were frobulated".withFields(["count", 5, "silly", true]); 83 | * // returns "Wobbles were frobulated [count=5, silly=true]" 84 | * ``` 85 | * 86 | * @param fields The field names and values to be formatted. Must have an even length. 87 | */ 88 | public static function withFields(message:String, fields:Array):String { 89 | var ll = fields.length; 90 | if (ll > 0) { 91 | message += (message.length > 0) ? " [" : "["; 92 | var ii = 0; 93 | while (ii < ll) { 94 | if (ii > 0) { 95 | message += ", "; 96 | } 97 | var name = fields[ii]; 98 | var value:Dynamic = fields[ii + 1]; 99 | 100 | // Replace throwables with their stack trace 101 | #if js 102 | if (Std.is(value, untyped __js__("Error"))) { 103 | var stack :String = value.stack; 104 | if (stack != null) { 105 | value = stack; 106 | } 107 | } 108 | #end 109 | message += name + "=" + value; 110 | ii += 2; 111 | } 112 | message += "]"; 113 | } 114 | 115 | return message; 116 | } 117 | } -------------------------------------------------------------------------------- /src/jasper/Variable.hx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013-2017, Nucleic Development Team. 3 | * Haxe Port Copyright (c) 2017 Jeremy Meltingtallow 4 | * 5 | * Distributed under the terms of the Modified BSD License. 6 | * 7 | * The full license is in the file COPYING.txt, distributed with this software. 8 | */ 9 | 10 | package jasper; 11 | 12 | class _Variable_ 13 | { 14 | public var m_name (default, null):String; 15 | public var m_value :Float; 16 | 17 | /** 18 | * [Description] 19 | * @param name - 20 | */ 21 | @:allow(jasper.Variable) 22 | private function new(name :String) : Void 23 | { 24 | this.m_name = name; 25 | this.m_value = 0.0; 26 | } 27 | 28 | /** 29 | * [Description] 30 | * @return String 31 | */ 32 | public function toString() : String 33 | { 34 | return "name: " + m_name + " value: " + m_value; 35 | } 36 | } 37 | 38 | //********************************************************************************************************* 39 | 40 | @:forward 41 | @:notNull 42 | abstract Variable(_Variable_) to _Variable_ 43 | { 44 | public inline function new(name :String = "") : Void 45 | { 46 | this = new _Variable_(name); 47 | } 48 | 49 | @:op(A*B) @:commutative static function multiplyValue(variable :Variable, coefficient :Value) : Term 50 | { 51 | return new Term( variable, coefficient ); 52 | } 53 | 54 | @:op(A/B) static function divideValue(variable :Variable, denominator :Value) : Term 55 | { 56 | return variable * ( 1.0 / denominator ); 57 | } 58 | 59 | @:op(-A) static function negateVariable(variable :Variable) : Term 60 | { 61 | return variable * -1.0; 62 | } 63 | 64 | @:op(A+B) static function addVariable(first :Variable, second :Variable) : Expression 65 | { 66 | return new Term(first) + second; 67 | } 68 | 69 | @:op(A+B) @:commutative static function addValue(variable :Variable, constant :Value) : Expression 70 | { 71 | return new Term(variable) + constant; 72 | } 73 | 74 | @:op(A-B) static function subtractExpression(variable :Variable, expression :Expression) : Expression 75 | { 76 | return variable + -expression; 77 | } 78 | 79 | @:op(A-B) static function subtractTerm(variable :Variable, term :Term) : Expression 80 | { 81 | return variable + -term; 82 | } 83 | 84 | @:op(A-B) static function subtractVariable(first :Variable, second :Variable) : Expression 85 | { 86 | return first + -second; 87 | } 88 | 89 | @:op(A-B) static function subtractValue(variable :Variable, constant :Value) : Expression 90 | { 91 | return variable + -constant; 92 | } 93 | 94 | @:op(A==B) static function equalsVariable(first :Variable, second :Variable) : Constraint 95 | { 96 | return new Term(first) == second; 97 | } 98 | 99 | @:op(A==B) @:commutative static function equalsValue(variable :Variable, constant :Value) : Constraint 100 | { 101 | return new Term(variable) == constant; 102 | } 103 | 104 | @:op(A<=B) static function lteExpression(variable :Variable, expression :Expression) : Constraint 105 | { 106 | return expression >= variable; 107 | } 108 | 109 | @:op(A<=B) static function lteTerm(variable :Variable, term :Term) : Constraint 110 | { 111 | return term >= variable; 112 | } 113 | 114 | @:op(A<=B) static function lteVariable(first :Variable, second :Variable) : Constraint 115 | { 116 | return new Term(first) <= second; 117 | } 118 | 119 | @:op(A<=B) static function lteValue(variable :Variable, constant :Value) : Constraint 120 | { 121 | return new Term(variable) <= constant; 122 | } 123 | 124 | @:op(A>=B) static function gteExpression(variable :Variable, expression :Expression) : Constraint 125 | { 126 | return expression <= variable; 127 | } 128 | 129 | @:op(A>=B) static function gteTerm(variable :Variable, term :Term) : Constraint 130 | { 131 | return term <= variable; 132 | } 133 | 134 | @:op(A>=B) static function gteVariable(first :Variable, second :Variable) : Constraint 135 | { 136 | return new Term(first) >= second; 137 | } 138 | 139 | @:op(A>=B) static function gteValue(variable :Variable, constant :Value) : Constraint 140 | { 141 | return new Term(variable) >= constant; 142 | } 143 | } -------------------------------------------------------------------------------- /src/jasper/Solver.hx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013-2017, Nucleic Development Team. 3 | * Haxe Port Copyright (c) 2017 Jeremy Meltingtallow 4 | * 5 | * Distributed under the terms of the Modified BSD License. 6 | * 7 | * The full license is in the file COPYING.txt, distributed with this software. 8 | */ 9 | 10 | package jasper; 11 | 12 | @:notNull 13 | abstract Solver(SolverImpl) 14 | { 15 | public inline function new() : Void 16 | { 17 | this = new SolverImpl(); 18 | } 19 | 20 | /** 21 | * Add a constraint to the solver. 22 | * 23 | * Throws 24 | * DuplicateConstraint: The given constraint has already been added to the solver. 25 | * UnsatisfiableConstraint: The given constraint is required and cannot be satisfied. 26 | * 27 | * @param constraint - 28 | */ 29 | public inline function addConstraint(constraint :Constraint) : Void 30 | { 31 | this.addConstraint( constraint ); 32 | } 33 | 34 | /** 35 | * Remove a constraint from the solver. 36 | * 37 | * Throws 38 | * UnknownConstraint: The given constraint has not been added to the solver. 39 | * 40 | * @param constraint - 41 | */ 42 | public inline function removeConstraint(constraint :Constraint) : Void 43 | { 44 | this.removeConstraint( constraint ); 45 | } 46 | 47 | /** 48 | * Test whether a constraint has been added to the solver. 49 | * 50 | * @param constraint - 51 | * @return Bool 52 | */ 53 | public inline function hasConstraint(constraint :Constraint) : Bool 54 | { 55 | return this.hasConstraint( constraint ); 56 | } 57 | 58 | /** 59 | * Add an edit variable to the solver. 60 | * 61 | * This method should be called before the `suggestValue` method is 62 | * used to supply a suggested value for the given edit variable. 63 | * 64 | * Throws 65 | * DuplicateEditVariable: The given edit variable has already been added to the solver. 66 | * BadRequiredStrength: The given strength is >= required. 67 | * 68 | * @param variable - 69 | * @param strength - 70 | */ 71 | public inline function addEditVariable(variable :Variable, strength :Strength) : Void 72 | { 73 | this.addEditVariable( variable, strength ); 74 | } 75 | 76 | /** 77 | * Remove an edit variable from the solver. 78 | * 79 | * Throws 80 | * UnknownEditVariable: The given edit variable has not been added to the solver. 81 | * 82 | * @param variable - 83 | */ 84 | public inline function removeEditVariable(variable :Variable) : Void 85 | { 86 | this.removeEditVariable( variable ); 87 | } 88 | 89 | /** 90 | * Test whether an edit variable has been added to the solver. 91 | * 92 | * @param variable - 93 | * @return Bool 94 | */ 95 | public inline function hasEditVariable(variable :Variable) : Bool 96 | { 97 | return this.hasEditVariable( variable ); 98 | } 99 | 100 | /** 101 | * Suggest a value for the given edit variable. 102 | * 103 | * This method should be used after an edit variable as been added to 104 | * the solver in order to suggest the value for that variable. After 105 | * all suggestions have been made, the `solve` method can be used to 106 | * update the values of all variables. 107 | * 108 | * Throws 109 | * UnknownEditVariable: The given edit variable has not been added to the solver. 110 | * 111 | * @param variable - 112 | * @param value - 113 | */ 114 | public inline function suggestValue(variable :Variable, value :Float) : Void 115 | { 116 | this.suggestValue( variable, value ); 117 | } 118 | 119 | /** 120 | * Update the values of the external solver variables. 121 | */ 122 | public inline function updateVariables() : Void 123 | { 124 | this.updateVariables(); 125 | } 126 | 127 | /** 128 | * Reset the solver to the empty starting condition. 129 | * 130 | * This method resets the internal solver state to the empty starting 131 | * condition, as if no constraints or edit variables have been added. 132 | * This can be faster than deleting the solver and creating a new one 133 | * when the entire system must change, since it can avoid unecessary 134 | * heap (de)allocations. 135 | */ 136 | public function reset() : Void 137 | { 138 | this.reset(); 139 | } 140 | 141 | /** 142 | * Dump a representation of the solver internals to stdout. 143 | */ 144 | public function dump() : Void 145 | { 146 | throw "dump"; 147 | } 148 | } -------------------------------------------------------------------------------- /test/gasm/core/EntityTest.hx: -------------------------------------------------------------------------------- 1 | package gasm.core; 2 | import buddy.BuddySuite; 3 | 4 | using buddy.Should; 5 | /** 6 | * ... 7 | * @author Leo Bergman 8 | */ 9 | class EntityTest extends BuddySuite { 10 | public function new() { 11 | describe("Entity", { 12 | describe("addChild", { 13 | it("should return child as added as firstChild when adding single child", { 14 | var base = new Entity(); 15 | var child = new Entity(); 16 | base.addChild(child); 17 | 18 | child.should.be(base.firstChild); 19 | }); 20 | 21 | it("should return first child added as firstChild when adding two children", { 22 | var base = new Entity(); 23 | var child = new Entity(); 24 | var child2 = new Entity(); 25 | base.addChild(child); 26 | base.addChild(child2); 27 | 28 | child.should.be(base.firstChild); 29 | }); 30 | 31 | it("should return sibling as next when adding two children", { 32 | var base = new Entity(); 33 | var child1 = new Entity(); 34 | base.addChild(child1); 35 | var child2 = new Entity(); 36 | base.addChild(child2); 37 | 38 | child2.should.be(child1.next); 39 | }); 40 | 41 | it("should return sibling as parent.firstChild when adding two children", { 42 | var base = new Entity(); 43 | var child1 = new Entity(); 44 | base.addChild(child1); 45 | var child2 = new Entity(); 46 | base.addChild(child2); 47 | 48 | child1.should.be(child2.parent.firstChild); 49 | }); 50 | }); 51 | 52 | describe("removeChild", { 53 | it("should remove added child", { 54 | var base = new Entity(); 55 | var child1 = new Entity(); 56 | base.addChild(child1); 57 | base.removeChild(child1); 58 | 59 | base.firstChild.should.be(null); 60 | child1.parent.should.be(null); 61 | }); 62 | it("should leave other children be", { 63 | var base = new Entity(); 64 | var child1 = new Entity(); 65 | var child2 = new Entity(); 66 | base.addChild(child1); 67 | base.addChild(child2); 68 | base.removeChild(child1); 69 | 70 | base.firstChild.should.be(child2); 71 | child1.parent.should.be(null); 72 | }); 73 | }); 74 | 75 | describe("disposeChildren", { 76 | it("should remove children from entity and their reference to parent", { 77 | var base = new Entity(); 78 | var child1 = new Entity(); 79 | var child2 = new Entity(); 80 | base.addChild(child1); 81 | base.addChild(child2); 82 | base.disposeChildren(); 83 | 84 | base.firstChild.should.be(null); 85 | child1.parent.should.be(null); 86 | child2.parent.should.be(null); 87 | }); 88 | 89 | it("should remain in graph and keep its reference to parent", { 90 | var base = new Entity(); 91 | var child = new Entity(); 92 | var grandchild = new Entity(); 93 | base.addChild(child); 94 | child.addChild(grandchild); 95 | child.disposeChildren(); 96 | 97 | base.firstChild.should.be(child); 98 | child.parent.should.be(base); 99 | grandchild.parent.should.be(null); 100 | }); 101 | }); 102 | 103 | describe("dispose", { 104 | it("should remove itself from parent", { 105 | var base = new Entity(); 106 | var child = new Entity(); 107 | base.addChild(child); 108 | child.dispose(); 109 | 110 | child.parent.should.be(null); 111 | base.firstChild.should.be(null); 112 | }); 113 | 114 | it("should remove own children", { 115 | var base = new Entity(); 116 | var child = new Entity(); 117 | var grandchild = new Entity(); 118 | base.addChild(child); 119 | child.addChild(grandchild); 120 | 121 | child.dispose(); 122 | 123 | child.firstChild.should.be(null); 124 | grandchild.parent.should.be(null); 125 | }); 126 | }); 127 | }); 128 | } 129 | } -------------------------------------------------------------------------------- /src/gasm/core/Component.hx: -------------------------------------------------------------------------------- 1 | package gasm.core; 2 | 3 | import gasm.core.enums.ComponentType; 4 | 5 | using tink.CoreApi; 6 | 7 | /** 8 | * ... 9 | * @author Leo Bergman 10 | */ 11 | #if (!macro && !display) 12 | @:autoBuild(gasm.core.macros.ComponentMacros.build()) 13 | #end 14 | @:componentAbstract 15 | class Component { 16 | @:allow(gasm.core) 17 | public var owner(default, null):Entity = null; 18 | 19 | @:allow(gasm.core) 20 | public var next(default, null):Component = null; 21 | 22 | // getter will will be completed by build macro in subclasses 23 | public var name(get, null):String; 24 | public var baseName(get, null):String; 25 | 26 | public var inited(default, default):Bool = false; 27 | 28 | public var componentType(default, null):ComponentType; 29 | public var enabled(get, set):Bool; 30 | 31 | var _scheduled:Array = []; 32 | var _predicates:Array = []; 33 | 34 | /** 35 | * Called when component been successfully added to entity. 36 | * Good place to do general setup, especially if it costly and is done before starting rendering. 37 | */ 38 | public function setup() {} 39 | 40 | /** 41 | * Called when component is just about to receive its first update. 42 | * Good place do things which require the whole Entity/Component graph to be set up, such as initializing things which depends on other components. 43 | */ 44 | public function init() {} 45 | 46 | /** 47 | Assign this to get callback when init is complete and component has had it's first update 48 | **/ 49 | dynamic public function onAdded() {} 50 | 51 | /** 52 | Remove this component from eventual owner. Will cause disposal if has an owner, else it is assumed component is already removed and disposed. 53 | 54 | @return True if component had an owner to be removed from 55 | **/ 56 | public inline function remove():Bool { 57 | final owned = owner != null; 58 | if (owned) { 59 | owner.remove(this); 60 | } 61 | return owned; 62 | } 63 | 64 | /** 65 | * Called when component is removed from entity. Called automatically on removal, so prefer using remove function. 66 | */ 67 | public function dispose() { 68 | _scheduled = []; 69 | } 70 | 71 | /** 72 | * Called when this component receives a game tick update. 73 | * @param dt Seconds elapsed since last tick. 74 | */ 75 | public function update(dt:Float) { 76 | for (item in _scheduled) { 77 | item.when -= dt; 78 | if (item.when <= 0) { 79 | item.trigger.trigger(Noise); 80 | _scheduled.remove(item); 81 | } 82 | } 83 | for (item in _predicates) { 84 | if (item.predicate()) { 85 | item.trigger.trigger(Noise); 86 | _predicates.remove(item); 87 | } 88 | } 89 | } 90 | 91 | /** 92 | * Schedule a future to be triggered 93 | * 94 | * NOTE: Depends on implementation calling super.update, which might not be the case with existing components since it was not needed before. 95 | * 96 | * @param delay Delay in ms after which future should trigger. 97 | **/ 98 | function after(delay = 0):Future { 99 | final trigger = Future.trigger(); 100 | _scheduled.push({when: (delay / 1000), trigger: trigger}); 101 | return trigger.asFuture(); 102 | } 103 | 104 | /** 105 | Schedule a future to trigger when a predicate is fulfilled 106 | 107 | @param predicate Function which when returning true will cause future to resolve 108 | @param timeout Number of seconds to wait for predicate to fultill and else resolve anyway. If unset or 0, future will never time out. 109 | **/ 110 | function when(predicate:() -> Bool, timeout = 0.0):Future { 111 | final trigger = Future.trigger(); 112 | var remain = timeout; 113 | var stamp = haxe.Timer.stamp(); 114 | var item:PredicateItem = null; 115 | 116 | final func = timeout > 0 ?() -> { 117 | final now = haxe.Timer.stamp(); 118 | remain -= now - stamp; 119 | stamp = now; 120 | final ready = predicate() || remain <= 0; 121 | if (ready) { 122 | _predicates.remove(item); 123 | } else { 124 | predicate(); 125 | } 126 | } : predicate; 127 | 128 | item = {predicate: func, trigger: trigger}; 129 | _predicates.push(item); 130 | return trigger.asFuture(); 131 | } 132 | 133 | /** 134 | * Overridden in subclasses by build macro 135 | * @return Name (package + class name) 136 | */ 137 | public function get_name():String { 138 | return null; 139 | } 140 | 141 | /** 142 | * Overridden in subclasses by build macro 143 | * @return Name (package + class name) 144 | */ 145 | public function get_baseName():String { 146 | return null; 147 | } 148 | 149 | /** 150 | * Override in subclass 151 | * @return True if enabled 152 | */ 153 | public function get_enabled():Bool { 154 | return null; 155 | } 156 | 157 | /** 158 | * Override in subclass 159 | * @param val Enable (true) or disable (false) 160 | */ 161 | public function set_enabled(val:Bool) { 162 | return null; 163 | } 164 | } 165 | 166 | typedef ScheduleItem = { 167 | when:Float, 168 | trigger:FutureTrigger, 169 | } 170 | 171 | typedef PredicateItem = { 172 | predicate:() -> Bool, 173 | trigger:FutureTrigger, 174 | } 175 | -------------------------------------------------------------------------------- /src/jasper/Term.hx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013-2017, Nucleic Development Team. 3 | * Haxe Port Copyright (c) 2017 Jeremy Meltingtallow 4 | * 5 | * Distributed under the terms of the Modified BSD License. 6 | * 7 | * The full license is in the file COPYING.txt, distributed with this software. 8 | */ 9 | 10 | package jasper; 11 | 12 | class _Term_ 13 | { 14 | public var m_variable (default, null):Variable; 15 | public var m_coefficient (default, null):Float; 16 | 17 | /** 18 | * [Description] 19 | * @param variable - 20 | * @param coefficient - 21 | */ 22 | @:allow(jasper.Term) 23 | private function new(variable :Variable, coefficient :Float) : Void 24 | { 25 | this.m_variable = variable; 26 | this.m_coefficient = coefficient; 27 | } 28 | 29 | /** 30 | * [Description] 31 | * @return Float 32 | */ 33 | public function value() : Float 34 | { 35 | return m_coefficient * m_variable.m_value; 36 | } 37 | 38 | /** 39 | * [Description] 40 | * @return String 41 | */ 42 | public function toString() : String 43 | { 44 | return "variable: (" + m_variable + ") coefficient: " + m_coefficient; 45 | } 46 | } 47 | 48 | //********************************************************************************************************* 49 | 50 | @:forward 51 | @:forwardStatics 52 | @:notNull 53 | abstract Term(_Term_) 54 | { 55 | public inline function new(variable :Variable, coefficient :Float = 1.0) : Void 56 | { 57 | this = new _Term_(variable, coefficient); 58 | } 59 | 60 | @:op(A*B) @:commutative static function multiplyValue(term :Term, coefficient :Value) : Term 61 | { 62 | return new Term( term.m_variable, term.m_coefficient * coefficient ); 63 | } 64 | 65 | @:op(A/B) static function divideValue(term :Term, denominator :Value) : Term 66 | { 67 | return term * ( 1.0 / denominator ); 68 | } 69 | 70 | @:op(-A) static function negateTerm(term :Term) : Term 71 | { 72 | return term * -1.0; 73 | } 74 | 75 | @:op(A+B) static function addTerm(first :Term, second :Term) : Expression 76 | { 77 | var terms = new Array(); 78 | terms.push(first); 79 | terms.push(second); 80 | 81 | return new Expression(terms); 82 | } 83 | 84 | @:op(A+B) @:commutative static function addVariable(term :Term, variable :Variable) : Expression 85 | { 86 | return term + new Term(variable); 87 | } 88 | 89 | @:op(A+B) @:commutative static function addValue(term :Term, constant :Value) : Expression 90 | { 91 | return new Expression([term], constant); 92 | } 93 | 94 | @:op(A-B) static function subtractExpression(term :Term, expression :Expression) : Expression 95 | { 96 | return -expression + term; 97 | } 98 | 99 | @:op(A-B) static function subtractTerm(first :Term, second :Term) : Expression 100 | { 101 | return first + -second; 102 | } 103 | 104 | @:op(A-B) static function subtractVariable(term :Term, variable :Variable) : Expression 105 | { 106 | return term + -variable; 107 | } 108 | 109 | @:op(A-B) static function subtractValue(term :Term, constant :Value) : Expression 110 | { 111 | return term + -constant; 112 | } 113 | 114 | @:op(A==B) static function equalsTerm(first :Term, second :Term) : Constraint 115 | { 116 | return new Expression([first]) == second; 117 | } 118 | 119 | @:op(A==B) @:commutative static function equalsVariable(term :Term, variable :Variable) : Constraint 120 | { 121 | return new Expression([term]) == variable; 122 | } 123 | 124 | @:op(A==B) @:commutative static function equalsValue(term :Term, constant :Value) : Constraint 125 | { 126 | return new Expression([term]) == constant; 127 | } 128 | 129 | @:op(A<=B) static function lteExpression(term :Term, expression :Expression) : Constraint 130 | { 131 | return expression >= term; 132 | } 133 | 134 | @:op(A<=B) static function lteTerm(first :Term, second :Term) : Constraint 135 | { 136 | return new Expression([first]) <= second; 137 | } 138 | 139 | @:op(A<=B) static function lteVariable(term :Term, variable :Variable) : Constraint 140 | { 141 | return new Expression([term]) <= variable; 142 | } 143 | 144 | @:op(A<=B) static function lteValue(term :Term, constant :Value) : Constraint 145 | { 146 | return new Expression([term]) <= constant; 147 | } 148 | 149 | @:op(A>=B) static function gteExpression(term :Term, expression :Expression) : Constraint 150 | { 151 | return expression <= term; 152 | } 153 | 154 | @:op(A>=B) static function gteTerm(first :Term, second :Term) : Constraint 155 | { 156 | return new Expression([first]) >= second; 157 | } 158 | 159 | @:op(A>=B) static function gteVariable(term :Term, variable :Variable) : Constraint 160 | { 161 | return new Expression([term]) >= variable; 162 | } 163 | 164 | @:op(A>=B) static function gteValue(term :Term, constant :Value) : Constraint 165 | { 166 | return new Expression([term]) >= constant; 167 | } 168 | } -------------------------------------------------------------------------------- /src/jasper/Row.hx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013-2017, Nucleic Development Team. 3 | * Haxe Port Copyright (c) 2017 Jeremy Meltingtallow 4 | * 5 | * Distributed under the terms of the Modified BSD License. 6 | * 7 | * The full license is in the file COPYING.txt, distributed with this software. 8 | */ 9 | 10 | package jasper; 11 | 12 | 13 | class Row 14 | { 15 | public var m_constant (default, null):Float; 16 | public var m_cells (default, null) = new Map(); 17 | 18 | public function new(constant :Float = 0) : Void 19 | { 20 | this.m_constant = constant; 21 | } 22 | 23 | public static inline function fromRow(other :Row) : Row 24 | { 25 | var row = new Row(other.m_constant); 26 | for(it in other.m_cells.keys()) { 27 | row.m_cells[it] = other.m_cells[it]; 28 | } 29 | return row; 30 | } 31 | 32 | /** 33 | * Add a constant value to the row constant. 34 | * The new value of the constant is returned. 35 | */ 36 | public function add(value :Float) : Float 37 | { 38 | return m_constant += value; 39 | } 40 | 41 | /** 42 | * Insert a symbol into the row with a given coefficient. 43 | * If the symbol already exists in the row, the coefficient will be 44 | * added to the existing coefficient. If the resulting coefficient 45 | * is zero, the symbol will be removed from the row. 46 | */ 47 | public function insertSymbol( symbol :Symbol, coefficient :Float = 1.0 ) : Void 48 | { 49 | if(!m_cells.exists(symbol)) { 50 | m_cells[symbol] = 0; 51 | } 52 | if( Util.nearZero( m_cells[ symbol ] += coefficient ) ) 53 | m_cells.remove( symbol ); 54 | } 55 | 56 | /** 57 | * Insert a row into this row with a given coefficient. 58 | * The constant and the cells of the other row will be multiplied by 59 | * the coefficient and added to this row. Any cell with a resulting 60 | * coefficient of zero will be removed from the row. 61 | */ 62 | public function insertRow( other :Row, coefficient :Float = 1.0 ) : Void 63 | { 64 | m_constant += other.m_constant * coefficient; 65 | for(it in other.m_cells.keys()) { 66 | if(!m_cells.exists(it)) { 67 | m_cells[it] = 0; 68 | } 69 | var coeff = other.m_cells[it] * coefficient; 70 | if( Util.nearZero( m_cells[ it ] += coeff ) ) 71 | m_cells.remove( it ); 72 | } 73 | } 74 | 75 | /** 76 | * Remove the given symbol from the row. 77 | */ 78 | public function remove( symbol :Symbol ) : Void 79 | { 80 | m_cells.remove(symbol); 81 | } 82 | 83 | /** 84 | * Reverse the sign of the constant and all cells in the row. 85 | */ 86 | public function reverseSign() : Void 87 | { 88 | m_constant = -m_constant; 89 | for( it in m_cells.keys() ) { 90 | m_cells[it] = -m_cells[it]; 91 | } 92 | } 93 | 94 | /** 95 | * Solve the row for the given symbol. 96 | * This method assumes the row is of the form a * x + b * y + c = 0 97 | * and (assuming solve for x) will modify the row to represent the 98 | * right hand side of x = -b/a * y - c / a. The target symbol will 99 | * be removed from the row, and the constant and other cells will 100 | * be multiplied by the negative inverse of the target coefficient. 101 | * The given symbol *must* exist in the row. 102 | */ 103 | public function solveFor( symbol :Symbol ) : Void 104 | { 105 | var coeff = -1.0 / m_cells[ symbol ]; 106 | m_cells.remove( symbol ); 107 | m_constant *= coeff; 108 | for( it in m_cells.keys()) { 109 | m_cells[it] *= coeff; 110 | } 111 | } 112 | 113 | /** 114 | * Solve the row for the given symbols. 115 | * This method assumes the row is of the form x = b * y + c and will 116 | * solve the row such that y = x / b - c / b. The rhs symbol will be 117 | * removed from the row, the lhs added, and the result divided by the 118 | * negative inverse of the rhs coefficient. 119 | * The lhs symbol *must not* exist in the row, and the rhs symbol 120 | * *must* exist in the row. 121 | */ 122 | public function solveForSymbols( lhs :Symbol, rhs :Symbol ) : Void 123 | { 124 | insertSymbol( lhs, -1.0 ); 125 | solveFor( rhs ); 126 | } 127 | 128 | /** 129 | * Get the coefficient for the given symbol. 130 | * If the symbol does not exist in the row, zero will be returned. 131 | */ 132 | public function coefficientFor( symbol :Symbol ) : Float 133 | { 134 | return m_cells.exists(symbol) ? m_cells[symbol] : 0; 135 | } 136 | 137 | /** 138 | * Substitute a symbol with the data from another row. 139 | * Given a row of the form a * x + b and a substitution of the 140 | * form x = 3 * y + c the row will be updated to reflect the 141 | * expression 3 * a * y + a * c + b. 142 | * If the symbol does not exist in the row, this is a no-op. 143 | */ 144 | public function substitute( symbol :Symbol, row :Row ) : Void 145 | { 146 | if( m_cells.exists( symbol ) ) 147 | { 148 | var coefficient = m_cells.get( symbol ); 149 | m_cells.remove( symbol ); 150 | insertRow( row, coefficient ); 151 | } 152 | } 153 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GASM ECS 2 | 3 | Framework agnostic ECS layer. 4 | 5 | ## What is it? 6 | Entity Component System heavily inspired by Flambe, but which is designed not to be tied to a particular renderer. So for example it could be used together with HEAPS, OpenFL, Pixi or Kha. 7 | The idea is to abstract the features that are common across frameworks, for example by having a sprite component tied to a sprite model with data such as x and y position. 8 | You can then add components modifying the sprite model without being tightly coupled to a Sprite class from a particular framework. 9 | 10 | This will remove a lot of the coupling between game code and rendering framework, but not to remove it completely since that would mean having to limit yourself to features that can reliably be replicated between frameworks. However it means that you can reuse a lot of components without modification, and should minimize the effort of porting a game to a different renderer. 11 | Targeting a new framework means writing a few classes to ensure that the Entity graph is tied to the rendering system and models for graphics and sound that can interface with the framework classes. 12 | 13 | Note that ECS purists will not consider this a proper ECS framework, since components contain the logic instead of systems. If you are writing a complex RPG or MMO, proper ECS might be worth looking in to, but for more typical small scale web or mobile projects I think having logic in components is preferable. 14 | 15 | ## Why should I use it 16 | There are several complete game engines using ECS, that are easy to work with and perform well (Flambe, Unity, Phaser, etc). 17 | However, when working with games that can have a commercial lifespan spanning decades, often they can outlast the technology which can turn very costly to address if the code is coupled to a specific stack. Haxe allows reaching many different platforms, and makes it possible to write code that transcends specific platforms, but typically you will still depend on for example OpenFL to abstract platform specific functionality. 18 | GASM takes this one step further, and makes it possible to with minimal effort switch to a new platform abstraction should the need arise. 19 | However, in the case of GASM the abstraction does add another layer of complexity and does cost some performance, so is essence there are two scenarios where GASM is a good choice: 20 | 1. You work with games that have a long lifespan, and want to abstract as much as possible from underlying technology to minimise risk and cost associated with having to port your games in the future. 21 | 2. You like the ECS flavor, and want to use it with your platform abstraction of choice. 22 | 23 | ## What does it mean? 24 | The name comes from the separation added to different component types, Graphics, Actor, Sound, Model. 25 | GASM has model types for graphics (SpriteModelComponent, TextModelComponent) and sound (SoundModelComponent) which are used to interface with the framework used. 26 | 27 | Regardless of in which order components are added, they will always be updated in the following order: 28 | Models -> Actors -> Graphics -> Sound 29 | 30 | ## Current status 31 | Project is under active development and the current focus is having a stable HEAPS adapter and fixing any issues we might encounter while making our first games using the framework. An OpenFL adapter exists, but is no longer maintained since we decided to go with HEAPS instead. 32 | Some optimization is done to ensure performance seems acceptable, and at least with the shallow entity graph in the bunny mark test in examples, the overhead introduced by the framwork seems like it should be acceptable for most games. Compared to Flambe there will be additional overhead due to two things: 33 | - The model components added to interface between graphics objects in different frameworks means extra calls when updating an Entity. 34 | - Extra logic to ensure the different component types are updated in the correct order. 35 | 36 | ## Future plans 37 | GASM will only handle parts which makes sense to abstract, and currently no additional features are planned. SOme more generally useful components might be added as the need arises, but for now there are none planned. 38 | 39 | ## Usage 40 | Run: 41 | ``` 42 | haxelib install gasm 43 | ``` 44 | Then add the integration for the backend you want to use: 45 | ``` 46 | haxelib install gasm-openfl 47 | ``` 48 | or 49 | ``` 50 | haxelib install gasm-heaps 51 | ``` 52 | 53 | Those are also available as npm packages, and assuming you already installed haxe-module-installer you can use: 54 | ``` 55 | npm i -D gasm 56 | npm i -D gasm-heaps 57 | npm i -D gasm-openfl 58 | ``` 59 | 60 | ## Getting started 61 | Have a look the the examples for some ideas of how to use GASM: 62 | https://github.com/HacksawStudios/GASM-examples 63 | 64 | ## Features 65 | 1. Core ECS implementation 66 | 67 | Entity and Component classes. 68 | 69 | 2. LayoutComponent 70 | 71 | Can be used to position and scale display objects to create layouts. 72 | 73 | 3. Loader 74 | 75 | Both HEAPS and OpenFL offer asset management, but they are designed to have assets available at compile time. If you need to do runtime loading you will have to make your own solution. In our case we do branded and localized games, and don't want assets to be compiled with the game. 76 | The Loader class can optionally be configured to handle branding, platform and locale specific resources with fallback while providing accurate loading progress. 77 | See Preloader class in gasm-heaps for an example of usage and preloader integration. 78 | 79 | ## Compatibility 80 | 81 | The goal is that the core ECS system should be possible to use for an integration on any target. 82 | So far only core entity component functionality is tested on Haxe 3.2.1 and 3.3.0 with the following targets: 83 | neko 84 | python 85 | node 86 | flash 87 | java 88 | cpp 89 | cs 90 | php -------------------------------------------------------------------------------- /src/gasm/core/components/SpriteModelComponent.hx: -------------------------------------------------------------------------------- 1 | package gasm.core.components; 2 | 3 | import gasm.core.Component; 4 | import gasm.core.enums.ComponentType; 5 | import gasm.core.enums.EventType; 6 | import gasm.core.events.InteractionEvent; 7 | import gasm.core.math.geom.Point; 8 | 9 | /** 10 | * Model to interface between different graphics backends. 11 | * Automatically added when you add ComponentType.GRAPHICS to an Entity. 12 | * 13 | * @author Leo Bergman 14 | */ 15 | class SpriteModelComponent extends Component { 16 | @:isVar public var x(default, set):Float = 0; 17 | @:isVar public var y(default, set):Float = 0; 18 | @:isVar public var alpha(default, set):Float = 1; 19 | @:isVar public var visible(get, set):Bool = true; 20 | public var width(get, set):Float; 21 | public var height(get, set):Float; 22 | public var xScale(get, set):Float; 23 | public var yScale(get, set):Float; 24 | public var origWidth(default, default):Float = -1; 25 | public var origHeight(default, default):Float = -1; 26 | public var mouseX(default, default):Float = 0; 27 | public var mouseY(default, default):Float = 0; 28 | public var stageMouseX(default, default):Float = 0; 29 | public var stageMouseY(default, default):Float = 0; 30 | public var stageSize(default, default):Point = {x: 0, y: 0}; 31 | public var offsetX(default, set):Float = 0; 32 | public var offsetY(default, set):Float = 0; 33 | public var speedX(default, default):Float; 34 | public var speedY(default, default):Float; 35 | public var interactive(default, default):Bool = false; 36 | public var mask(default, default):Any; 37 | public var dirty(default, default):Bool = true; 38 | 39 | var _width:Float = -1; 40 | var _height:Float = -1; 41 | var _xScale:Float = 1; 42 | var _yScale:Float = 1; 43 | var _pressHandlers(default, default):ArrayVoid>; 44 | var _overHandlers(default, default):ArrayVoid>; 45 | var _outHandlers(default, default):ArrayVoid>; 46 | var _moveHandlers(default, default):ArrayVoid>; 47 | var _dragHandlers(default, default):ArrayVoid>; 48 | var _downHandlers(default, default):ArrayVoid>; 49 | var _upHandlers(default, default):ArrayVoid>; 50 | var _resizeHandlers(default, default):ArrayVoid>; 51 | 52 | public function new() { 53 | componentType = ComponentType.GraphicsModel; 54 | _pressHandlers = []; 55 | _overHandlers = []; 56 | _outHandlers = []; 57 | _moveHandlers = []; 58 | _dragHandlers = []; 59 | _downHandlers = []; 60 | _upHandlers = []; 61 | _resizeHandlers = []; 62 | } 63 | 64 | override public function dispose() { 65 | for (type in Type.allEnums(EventType)) { 66 | removeHandlers(type); 67 | } 68 | _pressHandlers = null; 69 | _overHandlers = null; 70 | _outHandlers = null; 71 | _moveHandlers = null; 72 | _dragHandlers = null; 73 | _downHandlers = null; 74 | _upHandlers = null; 75 | _resizeHandlers = null; 76 | } 77 | 78 | public function addHandler(type:EventType, cb:InteractionEvent->Void) { 79 | var handlers = getHandlers(type); 80 | handlers.push(cb); 81 | } 82 | 83 | public function removeHandler(type:EventType, cb:InteractionEvent->Void) { 84 | var handlers = getHandlers(type); 85 | if (handlers != null) { 86 | for (handler in handlers) { 87 | if (handler == cb) { 88 | handlers.remove(handler); 89 | } 90 | } 91 | } 92 | } 93 | 94 | public function removeHandlers(type:EventType) { 95 | var handlers = getHandlers(type); 96 | handlers = []; 97 | } 98 | 99 | public function triggerEvent(type:EventType, point:{x:Float, y:Float}, owner:Entity) { 100 | var event = new InteractionEvent(point, owner); 101 | var handlers = getHandlers(type); 102 | for (handler in handlers) { 103 | handler(event); 104 | } 105 | } 106 | 107 | inline function getHandlers(type:EventType):ArrayVoid> { 108 | return switch (type) { 109 | case PRESS: _pressHandlers; 110 | case OVER: _overHandlers; 111 | case OUT: _outHandlers; 112 | case MOVE: _moveHandlers; 113 | case DRAG: _dragHandlers; 114 | case DOWN: _downHandlers; 115 | case UP: _upHandlers; 116 | case RESIZE: _resizeHandlers; 117 | } 118 | } 119 | 120 | public function set_x(val:Float) { 121 | x = val; 122 | dirty = true; 123 | return val; 124 | } 125 | 126 | public function set_y(val:Float) { 127 | y = val; 128 | dirty = true; 129 | return val; 130 | } 131 | 132 | public function set_offsetX(val:Float) { 133 | offsetX = val; 134 | dirty = true; 135 | return val; 136 | } 137 | 138 | public function set_offsetY(val:Float) { 139 | offsetY = val; 140 | dirty = true; 141 | return val; 142 | } 143 | 144 | public function get_width() { 145 | return _width; 146 | } 147 | 148 | public function set_width(val:Float) { 149 | _width = val; 150 | if (_width > 0 && origHeight > 0) { 151 | _xScale = val / origWidth; 152 | } 153 | dirty = true; 154 | return val; 155 | } 156 | 157 | public function get_height() { 158 | return _height; 159 | } 160 | 161 | public function set_height(val:Float) { 162 | _height = val; 163 | if (_height > 0 && origHeight > 0) { 164 | _yScale = val / origHeight; 165 | } 166 | dirty = true; 167 | return val; 168 | } 169 | 170 | public function get_xScale() { 171 | return _xScale; 172 | } 173 | 174 | public function set_xScale(val:Float) { 175 | _xScale = val; 176 | if (origWidth > 0) { 177 | _width = _xScale * origWidth; 178 | } 179 | dirty = true; 180 | return val; 181 | } 182 | 183 | public function get_yScale() { 184 | return _yScale; 185 | } 186 | 187 | public function set_yScale(val:Float) { 188 | _yScale = val; 189 | if (origHeight > 0) { 190 | _height = _yScale * origHeight; 191 | } 192 | dirty = true; 193 | return val; 194 | } 195 | 196 | public function get_visible() { 197 | return visible; 198 | } 199 | 200 | public function set_visible(val:Bool) { 201 | visible = val; 202 | dirty = true; 203 | return val; 204 | } 205 | 206 | public function set_alpha(val:Float) { 207 | alpha = val; 208 | dirty = true; 209 | return val; 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /src/jasper/Expression.hx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013-2017, Nucleic Development Team. 3 | * Haxe Port Copyright (c) 2017 Jeremy Meltingtallow 4 | * 5 | * Distributed under the terms of the Modified BSD License. 6 | * 7 | * The full license is in the file COPYING.txt, distributed with this software. 8 | */ 9 | 10 | package jasper; 11 | 12 | class _Expression_ 13 | { 14 | public var m_terms (default, null):Array; 15 | public var m_constant (default, null):Float; 16 | 17 | /** 18 | * [Description] 19 | * @param terms - 20 | * @param constant - 21 | */ 22 | @:allow(jasper.Expression) 23 | private function new(terms :Array, constant :Float) : Void 24 | { 25 | this.m_terms = terms; 26 | this.m_constant = constant; 27 | } 28 | 29 | /** 30 | * [Description] 31 | * @return Float 32 | */ 33 | public function value() : Float 34 | { 35 | var result :Float = m_constant; 36 | for(term in m_terms) 37 | result += term.value(); 38 | return result; 39 | } 40 | } 41 | 42 | //********************************************************************************************************* 43 | 44 | @:forward 45 | @:forwardStatics 46 | @:notNull 47 | abstract Expression(_Expression_) 48 | { 49 | public inline function new(terms :Array, constant :Float = 0.0) : Void 50 | { 51 | this = new jasper._Expression_(terms, constant); 52 | } 53 | 54 | @:op(A*B) @:commutative static function multiplyValue(expression :Expression, coefficient :Value) : Expression 55 | { 56 | var terms = new Array(); 57 | 58 | for (term in expression.m_terms) { 59 | terms.push(term * coefficient); 60 | } 61 | 62 | return new Expression(terms, expression.m_constant * coefficient); 63 | } 64 | 65 | @:op(A/B) static function divideValue(expression :Expression, denominator :Value) : Expression 66 | { 67 | return expression * ( 1.0 / denominator ); 68 | } 69 | 70 | @:op(-A) static function negateExpression(expression :Expression) : Expression 71 | { 72 | return expression * -1.0; 73 | } 74 | 75 | @:op(A+B) static function addExpression(first :Expression, second :Expression) : Expression 76 | { 77 | var terms = new Array(); 78 | 79 | for(t in first.m_terms) terms.push(t); 80 | for(t in second.m_terms) terms.push(t); 81 | 82 | return new Expression(terms, first.m_constant + second.m_constant); 83 | } 84 | 85 | @:op(A+B) @:commutative static function addTerm(first :Expression, second :Term) : Expression 86 | { 87 | var terms = new Array(); 88 | for(t in first.m_terms) terms.push(t); 89 | terms.push(second); 90 | return new Expression(terms, first.m_constant); 91 | } 92 | 93 | @:op(A+B) @:commutative static function addVariable(expression :Expression, variable :Variable) : Expression 94 | { 95 | return expression + new Term(variable); 96 | } 97 | 98 | @:op(A+B) @:commutative static function addValue(expression :Expression, constant :Value) : Expression 99 | { 100 | return new Expression( expression.m_terms, expression.m_constant + constant ); 101 | } 102 | 103 | @:op(A-B) static function subtractExpression(first :Expression, second :Expression) : Expression 104 | { 105 | return first + -second; 106 | } 107 | 108 | @:op(A-B) static function subtractTerm(expression :Expression, term :Term) : Expression 109 | { 110 | return expression + -term; 111 | } 112 | 113 | @:op(A-B) static function subtractVariable(expression :Expression, variable :Variable) : Expression 114 | { 115 | return expression + -variable; 116 | } 117 | 118 | @:op(A-B) static function subtractValue(expression :Expression, constant :Value) : Expression 119 | { 120 | return expression + -constant; 121 | } 122 | 123 | @:op(A==B) static function equalsExpression(first :Expression, second :Expression) : Constraint 124 | { 125 | return new Constraint( first - second, OP_EQ ); 126 | } 127 | 128 | @:op(A==B) @:commutative static function equalsTerm(expression :Expression, term :Term) : Constraint 129 | { 130 | return expression == new Expression([term]); 131 | } 132 | 133 | @:op(A==B) @:commutative static function equalsVariable(expression :Expression, variable :Variable) : Constraint 134 | { 135 | return expression == new Term(variable); 136 | } 137 | 138 | @:op(A==B) @:commutative static function equalsValue(expression :Expression, constant :Value) : Constraint 139 | { 140 | return expression == new Expression([], constant); 141 | } 142 | 143 | @:op(A<=B) static function lteExpression(first :Expression, second :Expression) : Constraint 144 | { 145 | return new Constraint( first - second, OP_LE ); 146 | } 147 | 148 | @:op(A<=B) static function lteTerm(expression :Expression, term :Term) : Constraint 149 | { 150 | return expression <= new Expression([term]); 151 | } 152 | 153 | @:op(A<=B) static function lteVariable(expression :Expression, variable :Variable) : Constraint 154 | { 155 | return expression <= new Term(variable); 156 | } 157 | 158 | @:op(A<=B) static function lteValue(expression :Expression, constant :Value) : Constraint 159 | { 160 | return expression <= new Expression([], constant); 161 | } 162 | 163 | @:op(A>=B) static function gteExpression(first :Expression, second :Expression) : Constraint 164 | { 165 | return new Constraint( first - second, OP_GE ); 166 | } 167 | 168 | @:op(A>=B) static function gteTerm(expression :Expression, term :Term) : Constraint 169 | { 170 | return expression >= new Expression([term]); 171 | } 172 | 173 | @:op(A>=B) static function gteVariable(expression :Expression, variable :Variable) : Constraint 174 | { 175 | return expression >= new Term(variable); 176 | } 177 | 178 | @:op(A>=B) static function gteValue(expression :Expression, constant :Value) : Constraint 179 | { 180 | return expression >= new Expression([], constant); 181 | } 182 | } -------------------------------------------------------------------------------- /test/gasm/core/EngineTest.hx: -------------------------------------------------------------------------------- 1 | package gasm.core; 2 | import buddy.BuddySuite; 3 | import gasm.core.systems.CoreSystem; 4 | import gasm.core.systems.ActorSystem; 5 | import gasm.core.Engine; 6 | import gasm.core.Component; 7 | import gasm.core.enums.SystemType; 8 | import gasm.core.enums.ComponentType; 9 | import gasm.core.ISystem; 10 | import gasm.core.System; 11 | import haxe.EnumFlags; 12 | 13 | using buddy.Should; 14 | /** 15 | * ... 16 | * @author Leo Bergman 17 | */ 18 | class EngineTest extends BuddySuite { 19 | 20 | public function new() { 21 | describe("Engine", { 22 | describe("constructor", { 23 | it("should add core and actor systems", { 24 | var systems:Array = []; 25 | var engine = new Engine(systems); 26 | systems[0].should.beType(CoreSystem); 27 | systems[1].should.beType(ActorSystem); 28 | 29 | }); 30 | it("should sort systems according to system type", { 31 | var systems:Array = [ 32 | new SoundSystem(), 33 | new RenderingSystem() 34 | ]; 35 | 36 | var engine = new Engine(systems); 37 | systems[2].should.beType(RenderingSystem); 38 | systems[3].should.beType(SoundSystem); 39 | }); 40 | }); 41 | 42 | describe("updateEntity", { 43 | it("should update GraphicsModelComponent before GraphicsComponent", { 44 | var systems:Array = [ 45 | new RenderingSystem() 46 | ]; 47 | 48 | var engine = new Engine(systems); 49 | var gc = new GraphicsComponent(); 50 | var mc = new GraphicsModelComponent(); 51 | engine.baseEntity.add(gc).add(mc); 52 | engine.tick(); 53 | // GraphicsComponent will set flag if GraphicsModelComponent been updated first 54 | gc.modelUpdated.should.be(true); 55 | }); 56 | 57 | it("should update SoundModelComponent before SoundComponent", { 58 | var systems:Array = [ 59 | new SoundSystem() 60 | ]; 61 | 62 | var engine = new Engine(systems); 63 | var sc = new SoundComponent(); 64 | var mc = new SoundModelComponent(); 65 | engine.baseEntity.add(sc).add(mc); 66 | engine.tick(); 67 | // SoundComponent will set flag if SoundModelComponent been updated first 68 | sc.modelUpdated.should.be(true); 69 | }); 70 | 71 | it("should update GraphicsComponent before SoundComponent", { 72 | var systems:Array = [ 73 | new SoundSystem(), 74 | new RenderingSystem() 75 | ]; 76 | 77 | var engine = new Engine(systems); 78 | var sc = new SoundComponent(); 79 | var gc = new GraphicsComponent(); 80 | engine.baseEntity.add(sc).add(gc); 81 | engine.tick(); 82 | // SoundComponent will set flag if GraphicsComponent been updated first 83 | sc.graphicsUpdated.should.be(true); 84 | }); 85 | }); 86 | }); 87 | } 88 | } 89 | 90 | class RenderingSystem extends System implements ISystem { 91 | public function new() { 92 | super(); 93 | type = SystemType.RENDERING; 94 | componentFlags.set(ComponentType.Graphics); 95 | componentFlags.set(ComponentType.Text); 96 | }; 97 | 98 | public function update(comp:Component, delta:Float):Void { 99 | if (!comp.inited) { 100 | comp.init(); 101 | comp.inited = true; 102 | } 103 | comp.update(delta); 104 | }; 105 | } 106 | 107 | class SoundSystem extends System implements ISystem { 108 | public function new() { 109 | super(); 110 | type = SystemType.SOUND; 111 | componentFlags.set(ComponentType.Sound); 112 | }; 113 | 114 | public function update(comp:Component, delta:Float):Void { 115 | if (!comp.inited) { 116 | comp.init(); 117 | comp.inited = true; 118 | } 119 | comp.update(delta); 120 | }; 121 | } 122 | 123 | class GraphicsComponent extends Component { 124 | public var updated:Bool; 125 | public var modelUpdated:Bool; 126 | 127 | public function new() { 128 | componentType = ComponentType.Graphics; 129 | } 130 | 131 | override public function update(dt:Float) { 132 | var model = owner.get(GraphicsModelComponent); 133 | if (model != null && model.updated) { 134 | modelUpdated = true; 135 | } 136 | updated = true; 137 | } 138 | } 139 | 140 | class GraphicsModelComponent extends Component { 141 | public var updated:Bool; 142 | 143 | public function new() { 144 | componentType = ComponentType.GraphicsModel; 145 | } 146 | 147 | override public function update(dt:Float) { 148 | updated = true; 149 | } 150 | } 151 | 152 | class SoundComponent extends Component { 153 | public var updated:Bool; 154 | public var modelUpdated:Bool; 155 | public var graphicsUpdated:Bool; 156 | 157 | public function new() { 158 | componentType = ComponentType.Sound; 159 | } 160 | 161 | override public function update(dt:Float) { 162 | var model = owner.get(SoundModelComponent); 163 | if (model != null && model.updated) { 164 | modelUpdated = true; 165 | } 166 | var graphics = owner.get(GraphicsComponent); 167 | if (graphics != null && graphics.updated) { 168 | graphicsUpdated = true; 169 | } 170 | updated = true; 171 | } 172 | } 173 | 174 | class SoundModelComponent extends Component { 175 | public var updated:Bool; 176 | 177 | public function new() { 178 | componentType = ComponentType.SoundModel; 179 | } 180 | 181 | override public function update(dt:Float) { 182 | updated = true; 183 | } 184 | } -------------------------------------------------------------------------------- /src/gasm/core/components/ThreeDModelComponent.hx: -------------------------------------------------------------------------------- 1 | package gasm.core.components; 2 | 3 | import gasm.core.Component; 4 | import gasm.core.enums.ComponentType; 5 | import gasm.core.enums.EventType; 6 | import gasm.core.events.InteractionEvent; 7 | import gasm.core.math.geom.Point; 8 | import gasm.core.math.geom.Vector; 9 | 10 | /** 11 | * Model to interface between different graphics backends. 12 | * Automatically added when you add ComponentType.Graphics3D to an Entity. 13 | * 14 | * @author Leo Bergman 15 | */ 16 | class ThreeDModelComponent extends Component { 17 | @:isVar public var pos(get, set) = new Vector(0, 0, 0); 18 | @:isVar public var offset(default, set) = new Vector(0, 0, 0); 19 | @:isVar public var scale(default, set) = new Vector(1., 1., 1.); 20 | @:isVar public var dimensions(default, set) = new Vector(0, 0, 0); 21 | @:isVar public var origDimensions(default, set) = new Vector(0, 0, 0); 22 | @:isVar public var alpha(default, set) = 1.; 23 | public var mouseX(default, default) = 0.; 24 | public var mouseY(default, default) = 0.; 25 | public var stageMouseX(default, default) = 0.; 26 | public var stageMouseY(default, default) = 0.; 27 | public var stageSize(default, default):Point = {x: 0, y: 0}; 28 | public var interactive(default, default) = false; 29 | public var visible(default, default) = true; 30 | public var mask(default, default):Any; 31 | public var dirty(default, default) = false; 32 | 33 | var _pressHandlers(default, default):ArrayVoid>; 34 | var _overHandlers(default, default):ArrayVoid>; 35 | var _outHandlers(default, default):ArrayVoid>; 36 | var _moveHandlers(default, default):ArrayVoid>; 37 | var _dragHandlers(default, default):ArrayVoid>; 38 | var _downHandlers(default, default):ArrayVoid>; 39 | var _upHandlers(default, default):ArrayVoid>; 40 | var _resizeHandlers(default, default):ArrayVoid>; 41 | 42 | public function new() { 43 | componentType = ComponentType.Graphics3DModel; 44 | _pressHandlers = []; 45 | _overHandlers = []; 46 | _outHandlers = []; 47 | _moveHandlers = []; 48 | _dragHandlers = []; 49 | _downHandlers = []; 50 | _upHandlers = []; 51 | _resizeHandlers = []; 52 | } 53 | 54 | override public function dispose() { 55 | for (type in Type.allEnums(EventType)) { 56 | removeHandlers(type); 57 | } 58 | _pressHandlers = null; 59 | _overHandlers = null; 60 | _outHandlers = null; 61 | _moveHandlers = null; 62 | _dragHandlers = null; 63 | _downHandlers = null; 64 | _upHandlers = null; 65 | _resizeHandlers = null; 66 | } 67 | 68 | public function addHandler(type:EventType, cb:InteractionEvent->Void) { 69 | var handlers = getHandlers(type); 70 | handlers.push(cb); 71 | } 72 | 73 | public function removeHandler(type:EventType, cb:InteractionEvent->Void) { 74 | var handlers = getHandlers(type); 75 | if (handlers != null) { 76 | for (handler in handlers) { 77 | if (handler == cb) { 78 | handlers.remove(handler); 79 | } 80 | } 81 | } 82 | } 83 | 84 | public function removeHandlers(type:EventType) { 85 | var handlers = getHandlers(type); 86 | handlers = []; 87 | } 88 | 89 | public function triggerEvent(type:EventType, point:{x:Float, y:Float, ?z:Float}, owner:Entity) { 90 | var event = new InteractionEvent(point, owner); 91 | var handlers = getHandlers(type); 92 | for (handler in handlers) { 93 | handler(event); 94 | } 95 | } 96 | 97 | inline function getHandlers(type:EventType):ArrayVoid> { 98 | return switch (type) { 99 | case PRESS: _pressHandlers; 100 | case OVER: _overHandlers; 101 | case OUT: _outHandlers; 102 | case MOVE: _moveHandlers; 103 | case DRAG: _dragHandlers; 104 | case DOWN: _downHandlers; 105 | case UP: _upHandlers; 106 | case RESIZE: _resizeHandlers; 107 | } 108 | } 109 | 110 | function get_pos():Vector { 111 | // Set to dirty, since we can modify vector positions in reference 112 | dirty = true; 113 | return pos; 114 | } 115 | 116 | function set_pos(val:Vector) { 117 | pos = val; 118 | dirty = true; 119 | return val; 120 | } 121 | 122 | function set_x(val:Float) { 123 | pos.x = val; 124 | dirty = true; 125 | return val; 126 | } 127 | 128 | function set_y(val:Float) { 129 | pos.y = val; 130 | dirty = true; 131 | return val; 132 | } 133 | 134 | function set_z(val:Float) { 135 | pos.z = val; 136 | dirty = true; 137 | return val; 138 | } 139 | 140 | function get_width() { 141 | return dimensions.x; 142 | } 143 | 144 | function set_width(val:Float) { 145 | dimensions.x = val; 146 | if (val > 0 && origDimensions.x > 0) { 147 | scale.x = val / origDimensions.x; 148 | } 149 | dirty = true; 150 | return val; 151 | } 152 | 153 | function get_height() { 154 | return dimensions.y; 155 | } 156 | 157 | function set_height(val:Float) { 158 | dimensions.y = val; 159 | if (val > 0 && origDimensions.y > 0) { 160 | scale.y = val / origDimensions.y; 161 | } 162 | dirty = true; 163 | return val; 164 | } 165 | 166 | function get_depth() { 167 | return dimensions.z; 168 | } 169 | 170 | function set_depth(val:Float) { 171 | dimensions.z = val; 172 | if (dimensions.z > 0 && origDimensions.z > 0) { 173 | scale.z = val / origDimensions.z; 174 | } 175 | dirty = true; 176 | return val; 177 | } 178 | 179 | function get_xScale() { 180 | return scale.x; 181 | } 182 | 183 | function set_xScale(val:Float) { 184 | scale.x = val; 185 | if (origDimensions.x > 0) { 186 | dimensions.x = scale.x * origDimensions.x; 187 | } 188 | dirty = true; 189 | return val; 190 | } 191 | 192 | function get_yScale() { 193 | return scale.y; 194 | } 195 | 196 | function set_yScale(val:Float) { 197 | scale.y = val; 198 | if (origDimensions.y > 0) { 199 | dimensions.y = scale.y * origDimensions.y; 200 | } 201 | dirty = true; 202 | return val; 203 | } 204 | 205 | function get_zScale() { 206 | return scale.z; 207 | } 208 | 209 | function set_zScale(val:Float) { 210 | scale.z = val; 211 | if (origDimensions.z > 0) { 212 | dimensions.z = scale.z * origDimensions.z; 213 | } 214 | dirty = true; 215 | return val; 216 | } 217 | 218 | function set_alpha(val:Float) { 219 | alpha = val; 220 | dirty = true; 221 | return val; 222 | } 223 | 224 | function set_dimensions(val:Vector) { 225 | dimensions = val; 226 | dirty = true; 227 | return val; 228 | } 229 | 230 | function set_origDimensions(val:Vector) { 231 | origDimensions = val; 232 | return val; 233 | } 234 | 235 | function set_offset(val:Vector) { 236 | offset = val; 237 | dirty = true; 238 | return val; 239 | } 240 | 241 | function set_scale(val:Vector) { 242 | scale = val; 243 | dirty = true; 244 | return val; 245 | } 246 | } 247 | -------------------------------------------------------------------------------- /src/gasm/core/Entity.hx: -------------------------------------------------------------------------------- 1 | package gasm.core; 2 | 3 | import gasm.core.components.SoundModelComponent; 4 | import gasm.core.components.SpriteModelComponent; 5 | import gasm.core.components.TextModelComponent; 6 | import gasm.core.components.ThreeDModelComponent; 7 | import gasm.core.utils.Assert; 8 | import haxe.macro.Expr.ExprOf; 9 | import haxe.macro.Expr; 10 | import haxe.macro.Type.ClassType; 11 | 12 | using Lambda; 13 | using haxe.macro.Tools; 14 | 15 | /** 16 | * A node in the entity hierarchy, and a collection of components. 17 | * 18 | * To iterate over the hierarchy, use the parent, firstChild, next and firstComponent fields. For 19 | * example: 20 | * 21 | * ```haxe 22 | * // Iterate over entity's children 23 | * var child = entity.firstChild; 24 | * while (child != null) { 25 | * var next = child.next; // Store in case the child is removed in process() 26 | * process(child); 27 | * child = next; 28 | * } 29 | * ``` 30 | */ 31 | @:final class Entity { 32 | /** This entity's parent. */ 33 | public var parent(default, null):Entity = null; 34 | 35 | /** This entity's first child. */ 36 | public var firstChild(default, null):Entity = null; 37 | 38 | /** This entity's next sibling, for iteration. */ 39 | public var next(default, null):Entity = null; 40 | 41 | /** This entity's first component. */ 42 | public var firstComponent(default, null):Component = null; 43 | 44 | private var _compMap:Map; 45 | 46 | public var id(default, null):String; 47 | 48 | public function new(id:String = "") { 49 | this.id = id; 50 | _compMap = new Map(); 51 | } 52 | 53 | /** 54 | * Add a component to this entity. Any previous component of this type will be replaced. 55 | * @returns This instance, for chaining. 56 | */ 57 | public function add(component:Component):Entity { 58 | var baseName = component.baseName; 59 | Assert.that(_compMap != null, 'Trying to add to disposed entity. $baseName'); 60 | if (component.owner != null) { 61 | component.owner.remove(component); 62 | } 63 | var prev = _compMap.get(baseName); 64 | if (prev != null) { 65 | remove(prev); 66 | } 67 | 68 | _compMap.set(baseName, component); 69 | 70 | var tail = null; 71 | var comp = firstComponent; 72 | 73 | while (comp != null) { 74 | tail = comp; 75 | comp = comp.next; 76 | } 77 | if (tail != null) { 78 | tail.next = component; 79 | } else { 80 | firstComponent = component; 81 | } 82 | component.owner = this; 83 | component.next = null; 84 | Assert.that(component.componentType != null, 'You need to set componentType in contructor of a Component'); 85 | switch component.componentType { 86 | case Graphics: 87 | add(new SpriteModelComponent()); 88 | case Text: 89 | add(new TextModelComponent()); 90 | case Graphics3D: 91 | add(new ThreeDModelComponent()); 92 | case Sound: 93 | add(new SoundModelComponent()); 94 | default: 95 | null; 96 | } 97 | component.setup(); 98 | return this; 99 | } 100 | 101 | /** 102 | * Remove a component from this entity. 103 | * @return Whether the component was removed. 104 | */ 105 | public function remove(component:Component):Bool { 106 | var prev:Component = null; 107 | var comp = firstComponent; 108 | while (comp != null) { 109 | var next = comp.next; 110 | if (comp == component) { 111 | // Splice out the component 112 | if (prev == null) { 113 | firstComponent = next; 114 | } else { 115 | prev.owner = this; 116 | prev.next = next; 117 | } 118 | if (_compMap != null) { 119 | _compMap.remove(comp.baseName); 120 | } 121 | if (comp.inited) { 122 | comp.dispose(); 123 | } 124 | comp.owner = null; 125 | comp.next = null; 126 | return true; 127 | } 128 | prev = comp; 129 | comp = next; 130 | } 131 | _compMap = null; 132 | return false; 133 | } 134 | 135 | #if (display || dox) 136 | public function get(componentClass:Class):T { 137 | return null; 138 | } 139 | #else 140 | macro public function get(self:Expr, componentClass:ExprOf>):ExprOf { 141 | var componentName = macro $componentClass.BASE_NAME; 142 | #if haxe4 143 | return macro Std.downcast($self.getComponentByName($componentName), $componentClass); 144 | #else 145 | return macro Std.instance($self.getComponentByName($componentName), $componentClass); 146 | #end 147 | } 148 | #end 149 | 150 | #if (display || dox) 151 | public function getFromParents(componentClass:Class):T { 152 | return null; 153 | } 154 | #else 155 | macro public function getFromParents(self:Expr, componentClass:ExprOf>):ExprOf { 156 | var name = macro $componentClass.BASE_NAME; 157 | return macro $self.getFromParentsByName($name, $componentClass); 158 | } 159 | #end 160 | 161 | #if (display || dox) 162 | public function getFromRoot(componentClass:Class):T { 163 | return null; 164 | } 165 | #else 166 | macro public function getFromRoot(self:Expr, componentClass:ExprOf>):ExprOf { 167 | var name = macro $componentClass.BASE_NAME; 168 | return macro $self.getFromRootByName($name, $componentClass); 169 | } 170 | #end 171 | 172 | inline public function getComponentByName(name:String):Component { 173 | return _compMap != null ? _compMap.get(name) : null; 174 | } 175 | 176 | public function getFromParentsByName(name:String, castToClass:Class):T { 177 | var entity = this; 178 | while (entity != null) { 179 | var component = entity.getComponentByName(name); 180 | if (component != null) { 181 | return cast component; 182 | } 183 | entity = entity.parent; 184 | }; 185 | return null; 186 | } 187 | 188 | public function getFromRootByName(name:String, castToClass:Class):T { 189 | var component:T = null; 190 | var retval:T = null; 191 | var p = this; 192 | var root = p; 193 | while (p != null) { 194 | component = cast p.getComponentByName(name); 195 | if (component != null) { 196 | retval = component; 197 | } 198 | p = p.parent; 199 | }; 200 | return cast retval; 201 | } 202 | 203 | /** 204 | * Adds a child to this entity. 205 | * @param append Whether to add the entity to the end or beginning of the child list. 206 | * @returns This instance, for chaining. 207 | */ 208 | public function addChild(entity:Entity, append:Bool = true):Entity { 209 | if (entity.parent != null) { 210 | entity.parent.removeChild(entity); 211 | } 212 | entity.parent = this; 213 | if (append) { 214 | var tail = null; 215 | var current = firstChild; 216 | while (current != null) { 217 | tail = current; 218 | current = current.next; 219 | } 220 | if (tail != null) { 221 | tail.next = entity; 222 | } else { 223 | firstChild = entity; 224 | } 225 | } else { 226 | entity.next = firstChild; 227 | firstChild = entity; 228 | } 229 | return this; 230 | } 231 | 232 | public function removeChild(entity:Entity) { 233 | var prev:Entity = null; 234 | var current = firstChild; 235 | while (current != null) { 236 | var next = current.next; 237 | if (current == entity) { 238 | if (prev == null) { 239 | firstChild = next; 240 | } else { 241 | prev.next = next; 242 | } 243 | current.parent = null; 244 | current.next = null; 245 | current.dispose(); 246 | return; 247 | } 248 | prev = current; 249 | current = next; 250 | } 251 | } 252 | 253 | /** 254 | * Dispose all of this entity's children, without touching its own components or removing itself 255 | * from its parent. 256 | */ 257 | public function disposeChildren() { 258 | while (firstChild != null) { 259 | var nextChild = firstChild.next; 260 | firstChild.dispose(); 261 | firstChild = nextChild; 262 | } 263 | } 264 | 265 | /** 266 | * Removes this entity from its parent, and disposes all its components and children. 267 | */ 268 | public function dispose() { 269 | if (parent != null) { 270 | parent.removeChild(this); 271 | parent = null; 272 | } 273 | if (firstComponent != null) { 274 | var nextComponent = firstComponent; 275 | while (nextComponent != null) { 276 | remove(nextComponent); 277 | nextComponent = nextComponent.next; 278 | } 279 | } 280 | disposeChildren(); 281 | } 282 | } 283 | -------------------------------------------------------------------------------- /test/gasm/core/EntityComponentTest.hx: -------------------------------------------------------------------------------- 1 | package gasm.core; 2 | 3 | import buddy.BuddySuite; 4 | import gasm.core.Entity; 5 | import gasm.core.Component; 6 | 7 | using buddy.Should; 8 | 9 | /** 10 | * Integration tests. Makes sense to test Component end Entity together since we want to properly test traversing the graph. 11 | * So rather than mocking entities in components and components in entities, we have on test for both. 12 | */ 13 | class EntityComponentTest extends BuddySuite { 14 | public function new() { 15 | // Excluding for now due to issues with buddy and haxe 4 preview-5 16 | @exclude describe("entity-component integration", { 17 | it("should return component added as firstComponent when adding single component", { 18 | var base = new Entity(); 19 | var comp = new TestComponent(); 20 | base.add(comp); 21 | comp.should.be(base.firstComponent); 22 | }); 23 | 24 | it("should return first component added as firstComponent when adding two different components", { 25 | var base = new Entity(); 26 | // we need to use different classes for different components, since there can only be one of each type. 27 | var comp1 = new TestComponentA(); 28 | var comp2 = new TestComponentB(); 29 | base.add(comp1); 30 | base.add(comp2); 31 | comp1.should.be(base.firstComponent); 32 | }); 33 | 34 | it("should return second component added as firstComponent when adding two identical components", { 35 | var base = new Entity(); 36 | var comp1 = new TestComponent(); 37 | var comp2 = new TestComponent(); 38 | base.add(comp1); 39 | base.add(comp2); 40 | comp2.should.be(base.firstComponent); 41 | }); 42 | 43 | /** 44 | * We don't want two classes with same super in one Entity, unless Component is super. 45 | * So we should use the class which is one step above Component for resolution. 46 | */ 47 | it("should return second component added as firstComponent when adding two components with same super", { 48 | var base = new Entity(); 49 | var comp1 = new TestComponentExtendsComponentA(); 50 | var comp2 = new TestComponentExtendsComponentB(); 51 | base.add(comp1); 52 | base.add(comp2); 53 | comp2.should.be(base.firstComponent); 54 | }); 55 | 56 | /** 57 | * If we extend classes with different base classes, they should result in en entity with two components. 58 | */ 59 | it("should return first component added as firstComponent when adding two components with different super", { 60 | var base = new Entity(); 61 | var comp1 = new TestComponentExtendsMockA(); 62 | var comp2 = new TestComponentExtendsMockB(); 63 | base.add(comp1); 64 | base.add(comp2); 65 | comp1.should.be(base.firstComponent); 66 | }); 67 | 68 | /** 69 | * If we add two components, we should be able to get component B in component A 70 | */ 71 | it("should be able to get sibling component from owner by class", { 72 | var base = new Entity(); 73 | var comp1 = new TestComponentA(); 74 | var comp2 = new TestComponentB(); 75 | base.add(comp1); 76 | base.add(comp2); 77 | comp2.should.be(comp1.owner.get(TestComponentB)); 78 | }); 79 | 80 | /** 81 | * If we add two components with the same base, they should count as same component type, and second component added should replace first. 82 | */ 83 | it("should get itself as component from component B when adding components with same base", { 84 | var base = new Entity(); 85 | var comp1 = new TestComponentExtendsComponentA(); 86 | var comp2 = new TestComponentExtendsComponentB(); 87 | base.add(comp1); 88 | base.add(comp2); 89 | comp2.should.be(comp2.owner.get(TestComponent)); 90 | }); 91 | 92 | /** 93 | * While resolution is done from the base class above Component, if you do use a subclass as get argument you have to use the same subclass as was added. 94 | */ 95 | it("should get null as component A from component B when adding components with same base", { 96 | var base = new Entity(); 97 | var comp1 = new TestComponentExtendsComponentA(); 98 | var comp2 = new TestComponentExtendsComponentB(); 99 | base.add(comp1); 100 | base.add(comp2); 101 | var match = comp2.owner.get(TestComponentExtendsComponentA); 102 | match.should.be(null); 103 | }); 104 | 105 | /** 106 | * If we add two components with the same base, we can use the base type to get the component. 107 | */ 108 | it("should get itself as component when added as component A", { 109 | var base = new Entity(); 110 | var comp1 = new TestComponentExtendsComponentA(); 111 | base.add(comp1); 112 | comp1.should.be(comp1.owner.get(TestComponent)); 113 | }); 114 | 115 | /** 116 | * If we add two components with the different base, they should count as different component types. 117 | * So when getting comp1 from comp2's owner, it should get a reference to comp1. 118 | */ 119 | it("should get component A from component B when adding components with different base", { 120 | var base = new Entity(); 121 | var comp1 = new TestComponentExtendsMockA(); 122 | var comp2 = new TestComponentExtendsMockB(); 123 | base.add(comp1); 124 | base.add(comp2); 125 | comp1.should.be(comp2.owner.get(TestComponentExtendsMockA)); 126 | }); 127 | 128 | it("should get component from immediate parent", { 129 | var base = new Entity(); 130 | var comp1 = new TestComponentA(); 131 | var comp2 = new TestComponentB(); 132 | var child = new Entity(); 133 | base.add(comp1); 134 | base.addChild(child); 135 | child.add(comp2); 136 | comp2.owner.getFromParents(TestComponentA).should.be(comp1); 137 | }); 138 | 139 | it("should not get component from sibling parent", { 140 | var base = new Entity(); 141 | var child = new Entity(); 142 | var sibling = new Entity(); 143 | var comp1 = new TestComponentA(); 144 | var comp2 = new TestComponentB(); 145 | base.addChild(child); 146 | base.addChild(sibling); 147 | sibling.add(comp1); 148 | child.add(comp2); 149 | comp2.owner.getFromParents(TestComponentA).should.be(null); 150 | }); 151 | 152 | it("should get component from grandparent", { 153 | var base = new Entity(); 154 | var child = new Entity(); 155 | var grandchild = new Entity(); 156 | var comp1 = new TestComponentA(); 157 | var comp2 = new TestComponentB(); 158 | base.addChild(child); 159 | child.addChild(grandchild); 160 | base.add(comp1); 161 | grandchild.add(comp2); 162 | comp2.owner.getFromParents(TestComponentA).should.be(comp1); 163 | }); 164 | 165 | it("should get component from root", { 166 | var root = new Entity(); 167 | var base = new Entity(); 168 | var child = new Entity(); 169 | var grandchild = new Entity(); 170 | var comp1 = new TestComponentA(); 171 | var comp2 = new TestComponentB(); 172 | root.addChild(base); 173 | base.addChild(child); 174 | child.addChild(grandchild); 175 | root.add(comp1); 176 | base.add(comp2); 177 | grandchild.getFromRoot(TestComponentA).should.be(comp1); 178 | }); 179 | 180 | it("should get component from one level up from root", { 181 | var root = new Entity(); 182 | var base = new Entity(); 183 | var child = new Entity(); 184 | var grandchild = new Entity(); 185 | var comp1 = new TestComponentA(); 186 | var comp2 = new TestComponentB(); 187 | root.addChild(base); 188 | base.addChild(child); 189 | child.addChild(grandchild); 190 | base.add(comp1); 191 | child.add(comp2); 192 | grandchild.getFromRoot(TestComponentA).should.be(comp1); 193 | }); 194 | 195 | it("should get component in self from root", { 196 | var root = new Entity(); 197 | var base = new Entity(); 198 | var child = new Entity(); 199 | var comp1 = new TestComponentA(); 200 | root.addChild(base); 201 | base.addChild(child); 202 | child.add(comp1); 203 | child.getFromRoot(TestComponentA).should.be(comp1); 204 | }); 205 | }); 206 | } 207 | } 208 | 209 | class TestComponent extends Component { 210 | public function new() {} 211 | } 212 | 213 | class TestComponentA extends Component { 214 | public function new() {} 215 | } 216 | 217 | class TestComponentB extends Component { 218 | public function new() {} 219 | } 220 | 221 | class TestComponentExtendsComponentA extends TestComponent {} 222 | class TestComponentExtendsComponentB extends TestComponent {} 223 | class TestComponentExtendsMockA extends TestComponentA {} 224 | class TestComponentExtendsMockB extends TestComponentB {} 225 | -------------------------------------------------------------------------------- /src/gasm/assets/Loader.hx: -------------------------------------------------------------------------------- 1 | package gasm.assets; 2 | 3 | import haxe.Http; 4 | import haxe.crypto.Base64; 5 | import haxe.ds.IntMap; 6 | import haxe.ds.StringMap; 7 | import haxe.io.Bytes; 8 | 9 | using Lambda; 10 | using StringTools; 11 | 12 | class Loader { 13 | var _imageFolder = 'image'; 14 | var _soundFolder = 'sound'; 15 | var _atlasFolder = 'atlas'; 16 | var _spineFolder = 'spine'; 17 | var _modelFolder = 'model'; 18 | var _gradientFolder = 'gradient'; 19 | var _fontFolder = 'font'; 20 | var _configFolder = 'config'; 21 | var _localizedFolder = 'localized'; 22 | var _defaultLocale = 'en'; 23 | var _commonFolder = 'common'; 24 | var _content:FileEntry; 25 | var _commonContent:FileEntry; 26 | var _platformContent:FileEntry; 27 | var _brandingContent:FileEntry; 28 | var _brandingCommon:FileEntry; 29 | var _brandingPlatform:FileEntry; 30 | var _platform:String; 31 | var _locale:String; 32 | var _extensionHandlers:IntMapVoid>; 33 | var _loadingQueue:Array; 34 | var _formats:Array; 35 | var _loadedBytes:StringMap; 36 | var _totalBytes:Int; 37 | var _totalItems = 0; 38 | var _loadedItems:Int; 39 | var _itemIndex = 0; 40 | 41 | /** 42 | * Create asset loader. 43 | * 44 | * Can handle scenarios where you have a skin/branding, multiple platform packs (for example mobile/desktop), localized assets and multiple file types. 45 | * 46 | * @param descriptorPath - Path to asset folder descriptor created with npm directory-tree 47 | * @param config - Loader configuration object 48 | */ 49 | public function new(descriptorPath:String, ?config:AssetConfig) { 50 | config = config != null ? config : {}; 51 | _platform = config.platform; 52 | _locale = config.locale; 53 | _formats = config.formats; 54 | _imageFolder = config.imageFolder != null ? config.imageFolder : _imageFolder; 55 | _soundFolder = config.soundFolder != null ? config.soundFolder : _soundFolder; 56 | _fontFolder = config.fontFolder != null ? config.fontFolder : _fontFolder; 57 | _atlasFolder = config.atlasFolder != null ? config.atlasFolder : _atlasFolder; 58 | _spineFolder = config.spineFolder != null ? config.spineFolder : _spineFolder; 59 | _modelFolder = config.modelFolder != null ? config.modelFolder : _modelFolder; 60 | _gradientFolder = config.gradientFolder != null ? config.gradientFolder : _gradientFolder; 61 | _configFolder = config.configFolder != null ? config.configFolder : _configFolder; 62 | _defaultLocale = config.defaultLocale != null ? config.defaultLocale : _defaultLocale; 63 | _commonFolder = config.commonFolder != null ? config.commonFolder : _commonFolder; 64 | _localizedFolder = config.localizedFolder != null ? config.localizedFolder : _localizedFolder; 65 | _extensionHandlers = new IntMapVoid>(); 66 | _loadingQueue = []; 67 | _loadedBytes = new StringMap(); 68 | var http = new Http(descriptorPath); 69 | http.onData = data -> { 70 | var parsedData = haxe.Json.parse(data); 71 | _content = cast parsedData.children.find(item -> item.name == 'default'); 72 | if (config.pack != 'default') { 73 | _brandingContent = cast parsedData.children.find(item -> item.name == config.pack); 74 | } 75 | _commonContent = _content.children.find(item -> item.name == _commonFolder); 76 | _platformContent = _content.children.find(item -> item.name == config.platform); 77 | if (_brandingContent != null) { 78 | _brandingCommon = _brandingContent.children.find(item -> item.name == _commonFolder); 79 | _brandingPlatform = _brandingContent.children.find(item -> item.name == config.platform); 80 | } 81 | onReady(); 82 | }; 83 | http.onError = function(error) { 84 | trace('error: $error'); 85 | }; 86 | http.request(); 87 | } 88 | 89 | public function load() { 90 | _loadedItems = 0; 91 | _totalBytes = _loadingQueue.fold((curr : QueueItem, last : Int) -> { 92 | var size = curr.size; 93 | if (curr.extras != null) { 94 | for (e in curr.extras) { 95 | size += e.size; 96 | } 97 | } 98 | return (size + last); 99 | }, 0); 100 | loadNext(); 101 | } 102 | 103 | public function addHandler(type:AssetType, handler:HandlerItem->Void) { 104 | _extensionHandlers.set(type.getIndex(), handler); 105 | } 106 | 107 | public function queueItem(id:String, type:AssetType) { 108 | var entry = getEntry(id, type); 109 | if (entry != null) { 110 | final extraTypes:Array = switch (type) { 111 | case AssetType.BitmapFont: [BitmapFontImage]; 112 | case AssetType.Atlas: [AtlasImage]; 113 | case AssetType.SpineAtlas: [SpineImage, SpineConfig]; 114 | default: []; 115 | } 116 | _totalItems++; 117 | 118 | final queueItem:QueueItem = { 119 | type: type, 120 | name: entry.name, 121 | path: entry.path, 122 | size: entry.size, 123 | extension: entry.extension, 124 | extras: null, 125 | }; 126 | 127 | if (entry.extras != null) { 128 | queueItem.extras = []; 129 | for (e in entry.extras) { 130 | final extraItem:QueueItem = { 131 | type: extraTypes[queueItem.extras.length], 132 | name: e.name, 133 | path: e.path, 134 | size: e.size, 135 | extension: e.extension, 136 | }; 137 | queueItem.extras.push(extraItem); 138 | 139 | _totalItems++; 140 | } 141 | } 142 | 143 | _loadingQueue.push(queueItem); 144 | } 145 | } 146 | 147 | dynamic public function onReady() {} 148 | 149 | dynamic public function onComplete() {} 150 | 151 | dynamic public function onProgress(percentDone:Int) {} 152 | 153 | dynamic public function onError(error:String) {} 154 | 155 | function loadNext() { 156 | if (_itemIndex < _loadingQueue.length) { 157 | var item = _loadingQueue[_itemIndex]; 158 | loadItem(item, _extensionHandlers.get(item.type.getIndex())); 159 | if (item.extras != null) { 160 | for (e in item.extras) { 161 | loadItem(e, _extensionHandlers.get(e.type.getIndex())); 162 | } 163 | } 164 | 165 | haxe.Timer.delay(() -> { 166 | _itemIndex++; 167 | loadNext(); 168 | }, 0); 169 | } 170 | } 171 | 172 | function loadItem(item:QueueItem, ?handler:HandlerItem->Void) { 173 | #if js 174 | var request = new js.html.XMLHttpRequest(); 175 | request.open('GET', item.path, true); 176 | request.responseType = js.html.XMLHttpRequestResponseType.ARRAYBUFFER; 177 | request.onload = event -> { 178 | if (request.status != 200) { 179 | onError(request.statusText); 180 | return; 181 | } 182 | _loadedItems++; 183 | var bytes = haxe.io.Bytes.ofData(request.response); 184 | switch (item.type) { 185 | case AssetType.Font: 186 | var fontResourceName = 'R_font_' + item.name; untyped { 187 | var s = js.Browser.document.createStyleElement(); 188 | s.type = "text/css"; 189 | s.innerHTML = "@font-face{ font-family: " + fontResourceName + "; src: url('data:font/ttf;base64," + Base64.encode(bytes) 190 | + "') format('truetype'); }"; 191 | js.Browser.document.getElementsByTagName('head')[0].appendChild(s); 192 | // create a div in the page to force font loading 193 | var div = js.Browser.document.createDivElement(); 194 | div.style.fontFamily = fontResourceName; 195 | div.style.opacity = 0; 196 | div.style.width = "1px"; 197 | div.style.height = "1px"; 198 | div.style.position = "fixed"; 199 | div.style.bottom = "0px"; 200 | div.style.right = "0px"; 201 | div.innerHTML = "."; 202 | div.className = "hx__loadFont"; 203 | js.Browser.document.body.appendChild(div); 204 | }; 205 | default: 206 | null; 207 | } 208 | if (handler != null) { 209 | handler({id: item.name, data: bytes, path: item.path}); 210 | } 211 | if (_loadedItems == _totalItems) { 212 | onComplete(); 213 | } 214 | }; 215 | request.onprogress = function(event:js.html.ProgressEvent) { 216 | var loaded = event.loaded; 217 | var total = event.total; 218 | handleProgress(Std.int(loaded), item.path, Std.int(total)); 219 | } 220 | request.send(null); 221 | #else 222 | throw 'NOT IMPLEMENTED'; 223 | #end 224 | } 225 | 226 | function getEntry(name:String, type:AssetType):FileEntry { 227 | var typeFolder = switch (type) { 228 | case AssetType.Image: _imageFolder; 229 | case AssetType.Sound: _soundFolder; 230 | case AssetType.Font | AssetType.BitmapFont: _fontFolder; 231 | case AssetType.Atlas: _atlasFolder; 232 | case AssetType.SpineAtlas: _spineFolder; 233 | case AssetType.Model: _modelFolder; 234 | case AssetType.Gradient: _gradientFolder; 235 | case AssetType.Config: _configFolder; 236 | default: null; 237 | } 238 | var platformFolder:FileEntry = null; 239 | if (_platformContent != null) { 240 | platformFolder = _platformContent.children.find(item -> item.name == typeFolder); 241 | } 242 | var brandingPlatformFolder:FileEntry = null; 243 | if (_brandingPlatform != null) { 244 | brandingPlatformFolder = _brandingPlatform.children.find(item -> item.name == typeFolder); 245 | } 246 | var brandingCommonFolder:FileEntry = null; 247 | 248 | if (_brandingCommon != null) { 249 | brandingCommonFolder = _brandingCommon.children.find(item -> item.name == typeFolder); 250 | } 251 | var commonFolder:FileEntry = _commonContent.children.find(item -> item.name == typeFolder); 252 | function getFilesFromFolder(folder:FileEntry, locale:String):Array { 253 | if (folder == null) { 254 | return null; 255 | } 256 | var matches:Array; 257 | var localized = folder.children.find(item -> item.name == _localizedFolder); 258 | if (localized != null) { 259 | var localeDir = localized.children.find(item -> item.name == locale && item.type == 'directory'); 260 | if (localeDir == null) { 261 | localeDir = localized.children.find(item -> item.name == _defaultLocale && item.type == 'directory'); 262 | } 263 | if (localeDir != null) { 264 | matches = findFilesByName(localeDir, name); 265 | } 266 | } 267 | if (matches == null || matches.length < 1) { 268 | matches = findFilesByName(folder, name); 269 | } 270 | return matches; 271 | } 272 | // Resolve files in following priority: Branding platform -> Branding common -> Default platform -> Default common 273 | var files = getFilesFromFolder(brandingPlatformFolder, _locale); 274 | if (files == null || files.length == 0) { 275 | files = getFilesFromFolder(brandingCommonFolder, _locale); 276 | if (files == null || files.length == 0) { 277 | files = getFilesFromFolder(platformFolder, _locale); 278 | if (files == null || files.length == 0) { 279 | files = getFilesFromFolder(commonFolder, _locale); 280 | } 281 | } 282 | } 283 | 284 | var entry:FileEntry = null; 285 | if (files != null) { 286 | if (files.length > 1) { 287 | switch (type) { 288 | case AssetType.BitmapFont: 289 | entry = files.find(val -> val.extension == '.xml' || val.extension == '.fnt'); 290 | final extra = files.find(val -> val.extension == '.png'); 291 | gasm.core.utils.Assert.that(extra != null, 'Unable to find bitmap font image.'); 292 | entry.extras = []; 293 | extra.type = 'file'; 294 | extra.path = extra.path.replace('\\', '/'); 295 | extra.name = getCleanFilename(extra.name); 296 | extra.size = extra.size != null ? Std.int(extra.size) : 0; 297 | entry.extras.push(extra); 298 | case AssetType.Atlas: 299 | entry = files.find(val -> val.extension == '.atlas'); 300 | var preferredExtension = getPreferredExtension(AssetType.AtlasImage); 301 | var extra = files.find(val -> val.extension == preferredExtension); 302 | if (extra == null) { 303 | extra = files.find(val -> val.extension == '.basis' || val.extension == '.png' || val.extension == '.jpg'); 304 | } 305 | gasm.core.utils.Assert.that(extra != null, 'Unable to find atlas image.'); 306 | entry.extras = []; 307 | extra.type = 'file'; 308 | extra.path = extra.path.replace('\\', '/'); 309 | extra.name = getCleanFilename(extra.name); 310 | extra.size = extra.size != null ? Std.int(extra.size) : 0; 311 | entry.extras.push(extra); 312 | case AssetType.SpineAtlas: 313 | entry = files.find(val -> val.extension == '.atlas'); 314 | 315 | // SpineImage 316 | var preferredExtension = getPreferredExtension(AssetType.SpineImage); 317 | var extra = files.find(val -> val.extension == preferredExtension); 318 | if (extra == null) { 319 | extra = files.find(val -> val.extension == '.png' || val.extension == '.jpg' || val.extension == '.basis'); 320 | } 321 | gasm.core.utils.Assert.that(extra != null, 'Unable to find spine image.'); 322 | entry.extras = []; 323 | extra.type = 'file'; 324 | extra.path = extra.path.replace('\\', '/'); 325 | extra.name = getCleanFilename(extra.name); 326 | extra.size = extra.size != null ? Std.int(extra.size) : 0; 327 | entry.extras.push(extra); 328 | 329 | // SpineConfig 330 | var extraConfig = files.find(val -> val.extension == '.json'); 331 | gasm.core.utils.Assert.that(extraConfig != null, 'Unable to find spine config.'); 332 | extraConfig.type = 'file'; 333 | extraConfig.path = extraConfig.path.replace('\\', '/'); 334 | extraConfig.name = getCleanFilename(extraConfig.name); 335 | extraConfig.size = extraConfig.size != null ? Std.int(extraConfig.size) : 0; 336 | entry.extras.push(extraConfig); 337 | default: 338 | var preferredExtension = getPreferredExtension(type); 339 | if (preferredExtension == null) { 340 | trace('Multiple files with same name found, but no preferred extension configured.'); 341 | trace('When constructing Loader add format param defining if you prefer to use ' 342 | + [for (match in files) match.extension].join(' or ') 343 | + ' for type ' 344 | + type.getName()); 345 | } 346 | entry = files.find(val -> val.extension == preferredExtension); 347 | } 348 | } else { 349 | entry = files[0]; 350 | } 351 | } 352 | if (entry == null) { 353 | return null; 354 | } 355 | entry.path = entry.path.replace('\\', '/'); 356 | entry.name = getCleanFilename(entry.name); 357 | entry.size = entry.size != null ? Std.int(entry.size) : 0; 358 | return entry; 359 | } 360 | 361 | inline function getCleanFilename(name:String):String { 362 | final len = name.lastIndexOf('.') != -1 ? name.lastIndexOf('.') : null; 363 | return name.substr(0, len); 364 | } 365 | 366 | inline function findFilesByName(dir:FileEntry, name:String):Array { 367 | return dir.children.filter(item -> getCleanFilename(item.name) == name && item.type == 'file'); 368 | } 369 | 370 | function handleProgress(position:Int, id:String, total:Int) { 371 | _loadedBytes.set(id, position); 372 | var loadedTotal = _loadedBytes.fold((curr : Int, last : Int) -> curr + last, 0); 373 | onProgress(Std.int((loadedTotal / _totalBytes) * 100)); 374 | } 375 | 376 | function getPreferredExtension(type:AssetType) { 377 | var fmt = _formats.find(val -> val.type == type); 378 | if (fmt != null) { 379 | return fmt.extension; 380 | } 381 | return null; 382 | } 383 | } 384 | 385 | typedef QueueItem = { 386 | name:String, 387 | type:AssetType, 388 | path:String, 389 | size:Int, 390 | extension:String, 391 | ?extras:Array, 392 | } 393 | 394 | typedef FormatType = { 395 | type:AssetType, 396 | extension:String, 397 | } 398 | 399 | enum AssetType { 400 | Image; 401 | Sound; 402 | Font; 403 | BitmapFont; 404 | BitmapFontImage; 405 | Config; 406 | Atlas; 407 | AtlasImage; 408 | Gradient; 409 | Model; 410 | SpineAtlas; 411 | SpineImage; 412 | SpineConfig; 413 | } 414 | 415 | typedef HandlerItem = { 416 | id:String, 417 | data:haxe.io.Bytes, 418 | ?path:String, 419 | } 420 | 421 | typedef AssetConfig = { 422 | /** 423 | * If specified, resources will resolved from this sub directory 424 | */ 425 | ?pack:String, 426 | /** 427 | * platform - If specified, resources will load from this platform folder 428 | */ 429 | ?platform:String, 430 | /** 431 | * If specfied, will look for a locale sub folder and prioritize assets in that folder 432 | */ 433 | ?locale:String, 434 | /** 435 | * Array with FormatTypes to define what extension to use if multiple files with same name is found. For example [{type:AssetType.Sound, extension:'.mp3'}] will ensure you only load mp3 audio 436 | */ 437 | ?formats:Array, 438 | /** 439 | * Name of folder containing images, defaults to 'image' 440 | */ 441 | ?imageFolder:String, 442 | /** 443 | * Name of folder containing sounds, defaults to 'sound' 444 | */ 445 | ?soundFolder:String, 446 | /** 447 | * Name of folder containing fonts, defaults to 'font' 448 | */ 449 | ?fontFolder:String, 450 | /** 451 | * Name of folder containing atlases, defaults to 'atlas' 452 | */ 453 | ?atlasFolder:String, 454 | /** 455 | * Name of folder containing spines, defaults to 'spine' 456 | */ 457 | ?spineFolder:String, 458 | /** 459 | * Name of folder containing models, defaults to 'model' 460 | */ 461 | ?modelFolder:String, 462 | /** 463 | * Folder for gradient .grd assets 464 | **/ 465 | ?gradientFolder:String, 466 | /** 467 | * Name of folder containing json config files, defaults to 'config' 468 | */ 469 | ?configFolder:String, 470 | /** 471 | * If locale has been set, this is the name of locale sub folder in which to look for localized assets. Defaults to 'localized' 472 | */ 473 | ?localizedFolder:String, 474 | /** 475 | * If locale for a resource is not found, this is the locale to fall back to. Dfeaults to 'en' 476 | */ 477 | ?defaultLocale:String, 478 | /** 479 | * Folder for non-platform specific assets 480 | **/ 481 | ?commonFolder:String, 482 | } 483 | -------------------------------------------------------------------------------- /src/gasm/core/components/LayoutComponent.hx: -------------------------------------------------------------------------------- 1 | package gasm.core.components; 2 | 3 | import gasm.core.api.singnals.TResize; 4 | import gasm.core.components.SpriteModelComponent; 5 | import gasm.core.enums.ComponentType; 6 | import gasm.core.enums.EventType; 7 | import gasm.core.enums.ScaleType; 8 | import gasm.core.events.InteractionEvent; 9 | import gasm.core.math.geom.Point; 10 | import gasm.core.utils.Assert; 11 | import gasm.core.utils.Log; 12 | import gasm.core.utils.SignalConnection; 13 | import jasper.Solver; 14 | import jasper.Variable; 15 | 16 | class LayoutComponent extends Component { 17 | public var computedMargins(get, null):Margins; 18 | 19 | public function get_computedMargins():Margins { 20 | return calculateMargins(layoutBox.margins, parent); 21 | } 22 | 23 | public var layoutBox(default, null):LayoutBox; 24 | public var spriteModel(default, null):SpriteModelComponent; 25 | public var isRoot(default, null):Bool; 26 | public var freeze(default, default):Bool; 27 | public var constraints:Constraints; 28 | public var parent:LayoutComponent; 29 | 30 | var _appModel:AppModelComponent; 31 | var _computedPadding:Size; 32 | var _displayDelay:Int; 33 | var _parentBox:LayoutBox; 34 | var _children:Array; 35 | var _resizeConnection:SignalConnection; 36 | 37 | public function new(box:LayoutBox, displayDelay:Int = 0) { 38 | layoutBox = box; 39 | layoutBox.margins = initMargins(layoutBox.margins); 40 | if (layoutBox.dock == null) { 41 | layoutBox.dock = Dock.NONE; 42 | } 43 | layoutBox.flow = layoutBox.flow != null ? layoutBox.flow : Flow.HORIZONTAL; 44 | layoutBox.vAlign = layoutBox.vAlign != null ? layoutBox.vAlign : Align.MID; 45 | layoutBox.hAlign = layoutBox.hAlign != null ? layoutBox.hAlign : Align.MID; 46 | constraints = { 47 | x: new Variable("x"), 48 | y: new Variable("y"), 49 | containerW: new Variable("containerW"), 50 | containerH: new Variable("containerH"), 51 | leftMarg: new Variable('leftMarg'), 52 | rightMarg: new Variable('rightMarg'), 53 | topMarg: new Variable('topMarg'), 54 | bottomMarg: new Variable('bottomMarg'), 55 | left: new Variable('left'), 56 | center: new Variable('center'), 57 | right: new Variable('right'), 58 | top: new Variable('top'), 59 | middle: new Variable('middle'), 60 | bottom: new Variable('bottom'), 61 | contentW: new Variable('contentW'), 62 | contentH: new Variable('contentH'), 63 | ypos: new Variable('ypos'), 64 | xpos: new Variable('xpos'), 65 | yMarg: new Variable('yMarg'), 66 | xMarg: new Variable('xMarg'), 67 | parentW: new Variable('parentW'), 68 | parentH: new Variable('parentH'), 69 | xScale: new Variable('xScale'), 70 | yScale: new Variable('yScale'), 71 | }; 72 | _displayDelay = displayDelay; 73 | _children = []; 74 | componentType = ComponentType.Actor; 75 | } 76 | 77 | override public function init() { 78 | spriteModel = owner.get(SpriteModelComponent); 79 | if (spriteModel == null) { 80 | spriteModel = cast owner.get(TextModelComponent); 81 | if (spriteModel == null) { 82 | spriteModel = new SpriteModelComponent(); 83 | } 84 | } 85 | 86 | _appModel = owner.getFromParents(AppModelComponent); 87 | Assert.that(_appModel != null, 'No AppModelComponent in graph. Check that your gasm integration context is adding it.'); 88 | 89 | parent = owner.parent != null ? owner.parent.getFromParents(LayoutComponent) : null; 90 | if (parent != null) { 91 | parent.addChild(this); 92 | _parentBox = parent.layoutBox; 93 | } else { 94 | isRoot = true; 95 | } 96 | 97 | _resizeConnection = _appModel.resizeSignal.connect(function(size:TResize) { 98 | layout(); 99 | }); 100 | 101 | spriteModel.addHandler(EventType.RESIZE, onResize); 102 | layout(); 103 | 104 | if (_displayDelay > 0) { 105 | var visibility = spriteModel.visible; 106 | spriteModel.visible = false; 107 | haxe.Timer.delay(function() { 108 | spriteModel.visible = visibility; 109 | }, _displayDelay); 110 | } 111 | } 112 | 113 | override public function dispose():Void { 114 | freeze = true; 115 | if (spriteModel != null) { 116 | spriteModel.dispose(); 117 | } 118 | _children = null; 119 | constraints = null; 120 | parent = null; 121 | if (_resizeConnection != null) { 122 | _resizeConnection.dispose(); 123 | } 124 | super.dispose(); 125 | } 126 | 127 | /** 128 | * Perform layout. Will be called automatically on resize events. 129 | **/ 130 | public function layout() { 131 | if (freeze) { 132 | return; 133 | } 134 | scale(); 135 | } 136 | 137 | function addChild(child:LayoutComponent) { 138 | _children.push(child); 139 | freeze = false; 140 | layout(); 141 | } 142 | 143 | function scale() { 144 | if (spriteModel == null) { 145 | haxe.Timer.delay(layout, 50); 146 | return; 147 | } 148 | 149 | calculateMargins(layoutBox.margins, parent); 150 | calculatePadding(); 151 | 152 | var ypos = 0.0; 153 | var xpos = 0.0; 154 | var xMarg = 0.0; 155 | var yMarg = 0.0; 156 | 157 | var dockedLeft = getDocked(Dock.LEFT); 158 | var dockedTop = getDocked(Dock.TOP); 159 | var dockedRight = getDocked(Dock.RIGHT); 160 | var dockedBottom = getDocked(Dock.BOTTOM); 161 | var undocked = getDocked(Dock.NONE); 162 | var allChildren:Array = dockedLeft.concat(dockedRight).concat(dockedTop).concat(dockedBottom).concat(undocked); 163 | for (comp in allChildren) { 164 | if (!comp.inited) { 165 | haxe.Timer.delay(scale, 1); 166 | return; 167 | } 168 | } 169 | if (!(allChildren.length > 0)) { 170 | return; 171 | } 172 | var parentSize = getComponentSize(parent); 173 | var size = getComponentSize(this); 174 | var scale = getComponentScale(this); 175 | 176 | for (layoutComp in dockedTop) { 177 | var c = layoutComp.constraints; 178 | if (c == null) 179 | continue; 180 | var solver = new Solver(); 181 | solver.addConstraint(c.xMarg == xMarg); 182 | solver.addConstraint(c.yMarg == yMarg); 183 | solver.addConstraint(c.xpos == xpos); 184 | solver.addConstraint(c.ypos == ypos); 185 | 186 | solver.addConstraint(c.left == c.leftMarg); 187 | solver.addConstraint(c.right <= c.containerW - (c.contentW + c.rightMarg)); 188 | solver.addConstraint(c.center == c.left + (c.containerW - (c.contentW + c.leftMarg + c.rightMarg)) / 2); 189 | solver.addConstraint(c.top == c.ypos + c.topMarg); 190 | solver.addConstraint(c.bottom == c.ypos + c.containerH - (c.contentH + c.bottomMarg)); 191 | solver.addConstraint(c.middle == c.top + (c.containerH - (c.contentH + c.topMarg + c.bottomMarg)) / 2); 192 | layoutItem(layoutComp, solver, constraints, size, parentSize, scale, Flow.VERTICAL); 193 | ypos += c.containerH.m_value + _computedPadding.value; 194 | } 195 | for (layoutComp in dockedBottom) { 196 | var c = layoutComp.constraints; 197 | if (c == null) 198 | continue; 199 | var solver = new Solver(); 200 | solver.addConstraint(c.xMarg == xMarg); 201 | solver.addConstraint(c.yMarg == yMarg); 202 | solver.addConstraint(c.xpos == xpos); 203 | solver.addConstraint(c.ypos == ypos); 204 | 205 | solver.addConstraint(c.left == c.leftMarg); 206 | solver.addConstraint(c.right == c.containerW - (c.contentW + c.rightMarg)); 207 | solver.addConstraint(c.center == c.left + (c.containerW - (c.contentW)) / 2); 208 | solver.addConstraint(c.top == c.topMarg + c.parentH - (c.containerH)); 209 | solver.addConstraint(c.middle == c.parentH - (c.yMarg + c.containerH - (c.containerH - c.contentH) / 2)); 210 | solver.addConstraint(c.bottom == c.parentH - (c.yMarg + c.containerH - (c.contentH - c.bottomMarg))); 211 | layoutItem(layoutComp, solver, constraints, size, parentSize, scale, Flow.VERTICAL); 212 | yMarg += c.containerH.m_value + _computedPadding.value + c.topMarg.m_value + c.bottomMarg.m_value; 213 | } 214 | for (layoutComp in dockedLeft) { 215 | var c = layoutComp.constraints; 216 | if (c == null) 217 | continue; 218 | var solver = new Solver(); 219 | solver.addConstraint(c.xMarg == xMarg); 220 | solver.addConstraint(c.yMarg == yMarg); 221 | solver.addConstraint(c.xpos == xpos); 222 | solver.addConstraint(c.ypos == ypos); 223 | 224 | solver.addConstraint(c.left == c.xpos + c.leftMarg); 225 | solver.addConstraint(c.right <= c.xpos + c.containerW - (c.contentW + c.rightMarg)); 226 | solver.addConstraint(c.center == c.xpos + c.left + (c.containerW - (c.contentW + c.leftMarg + c.rightMarg + c.xpos)) / 2); 227 | solver.addConstraint(c.top >= c.ypos + c.topMarg); 228 | solver.addConstraint(c.middle == c.top + (c.containerH - c.contentH) / 2); 229 | solver.addConstraint(c.bottom <= c.containerH - (c.contentH + c.bottomMarg + c.ypos)); 230 | layoutItem(layoutComp, solver, constraints, size, parentSize, scale, Flow.HORIZONTAL); 231 | xpos += c.containerW.m_value + _computedPadding.value + c.leftMarg.m_value + c.rightMarg.m_value; 232 | } 233 | for (layoutComp in dockedRight) { 234 | var c = layoutComp.constraints; 235 | if (c == null) 236 | continue; 237 | var solver = new Solver(); 238 | solver.addConstraint(c.xMarg == xMarg); 239 | solver.addConstraint(c.yMarg == yMarg); 240 | solver.addConstraint(c.xpos == xpos); 241 | solver.addConstraint(c.ypos == ypos); 242 | 243 | solver.addConstraint(c.left == (c.leftMarg + c.parentW - (c.containerW + c.xMarg))); 244 | solver.addConstraint(c.right == c.parentW - (c.contentW + c.rightMarg + c.xMarg)); 245 | solver.addConstraint(c.center == c.leftMarg 246 | - c.xMarg 247 | + c.parentW 248 | - c.containerW 249 | + ((c.containerW - (c.contentW + c.leftMarg + c.rightMarg)) / 2)); 250 | solver.addConstraint(c.top >= c.ypos + c.topMarg); 251 | solver.addConstraint(c.middle == c.top + (c.containerH - (c.contentH + c.topMarg + c.bottomMarg)) / 2); 252 | solver.addConstraint(c.bottom == c.containerH - (c.contentH + c.bottomMarg)); 253 | layoutItem(layoutComp, solver, constraints, size, parentSize, scale, Flow.HORIZONTAL); 254 | xMarg += c.containerW.m_value + _computedPadding.value + c.leftMarg.m_value + c.rightMarg.m_value; 255 | } 256 | 257 | for (layoutComp in undocked) { 258 | var c = layoutComp.constraints; 259 | if (c == null) 260 | continue; 261 | var solver = new Solver(); 262 | solver.addConstraint(c.xMarg == xMarg); 263 | solver.addConstraint(c.yMarg == yMarg); 264 | solver.addConstraint(c.xpos == xpos); 265 | solver.addConstraint(c.ypos == ypos); 266 | 267 | solver.addConstraint(c.left == c.xpos + c.leftMarg); 268 | solver.addConstraint(c.right <= c.xpos + c.leftMarg + c.parentW - (c.contentW + c.rightMarg + c.leftMarg)); 269 | solver.addConstraint(c.center == c.xpos + c.left + (c.parentW - (c.contentW + c.leftMarg + c.rightMarg)) / 2); 270 | solver.addConstraint(c.top == c.ypos + c.topMarg); 271 | solver.addConstraint(c.bottom <= c.ypos + c.containerH - (c.yMarg + c.contentH + c.bottomMarg)); 272 | solver.addConstraint(c.middle == c.ypos + c.top + (c.containerH - (c.contentH + c.bottomMarg + c.topMarg)) / 2); 273 | 274 | layoutItem(layoutComp, solver, constraints, size, parentSize, scale, layoutComp.layoutBox.flow); 275 | if (layoutBox.flow == Flow.VERTICAL) { 276 | ypos += c.containerH.m_value + _computedPadding.value; 277 | } else { 278 | xpos += c.containerW.m_value + _computedPadding.value + c.leftMarg.m_value + c.rightMarg.m_value; 279 | } 280 | } 281 | } 282 | 283 | function layoutItem(layoutComp:LayoutComponent, solver:Solver, parentConstraints:Constraints, size:Point, parentSize:Point, scale:Point, flow:Flow) { 284 | if (layoutComp.spriteModel == null) { 285 | Log.warn('Attempting to layout en element which does not have a sprite model.'); 286 | return; 287 | } 288 | var c = layoutComp.constraints; 289 | if (layoutComp.layoutBox.dimensions != null) { 290 | layoutComp.spriteModel.origWidth = layoutComp.layoutBox.dimensions.x; 291 | layoutComp.spriteModel.origHeight = layoutComp.layoutBox.dimensions.y; 292 | } 293 | var childBox = layoutComp.layoutBox; 294 | var childMargins = layoutComp.computedMargins; 295 | var xMargins = childMargins.right.value + childMargins.left.value; 296 | var yMargins = childMargins.top.value + childMargins.bottom.value; 297 | var parentW = constraints.contentW.m_value > 0 ? constraints.contentW.m_value / constraints.xScale.m_value : parentSize.x; 298 | var parentH = constraints.contentH.m_value > 0 ? constraints.contentH.m_value / constraints.yScale.m_value : parentSize.y; 299 | solver.addConstraint(c.parentW == parentW); 300 | solver.addConstraint(c.parentH == parentH); 301 | solver.addConstraint(c.leftMarg == childMargins.left.value); 302 | solver.addConstraint(c.rightMarg == childMargins.right.value); 303 | solver.addConstraint(c.topMarg == childMargins.top.value); 304 | solver.addConstraint(c.bottomMarg == childMargins.bottom.value); 305 | 306 | var containerW:Float; 307 | var containerH:Float; 308 | var hasDimensions = layoutComp.spriteModel.origWidth > 0 && layoutComp.spriteModel.origHeight > 0; 309 | if (childBox.size == null) { 310 | if (hasDimensions) { 311 | switch (flow) { 312 | case Flow.VERTICAL: 313 | childBox.size = {value: layoutComp.spriteModel.origHeight}; 314 | case Flow.HORIZONTAL: 315 | childBox.size = {value: layoutComp.spriteModel.origWidth}; 316 | } 317 | } 318 | } 319 | if (childBox.size != null) { 320 | switch (flow) { 321 | case Flow.VERTICAL: 322 | containerW = parentW; 323 | if (childBox.size.percent) { 324 | containerH = (childBox.size.value * parentH) / 100; 325 | } else { 326 | containerH = childBox.size.value; 327 | } 328 | case Flow.HORIZONTAL: 329 | containerH = parentH; 330 | if (childBox.size.percent) { 331 | containerW = (childBox.size.value * parentW) / 100; 332 | } else { 333 | containerW = childBox.size.value; 334 | } 335 | default: 336 | containerW = parentW; 337 | containerH = parentH; 338 | } 339 | } else { 340 | containerW = parentSize.x; 341 | containerH = parentSize.y; 342 | } 343 | 344 | var scaledH:Float; 345 | var scaledW:Float; 346 | var xScale:Float = 1.0; 347 | var yScale:Float = 1.0; 348 | if (childBox.scale == ScaleType.PROPORTIONAL) { 349 | var origW = layoutComp.spriteModel.origWidth > 0 ? layoutComp.spriteModel.origWidth : size.x; 350 | var origH = layoutComp.spriteModel.origHeight > 0 ? layoutComp.spriteModel.origHeight : size.y; 351 | var ratio = Math.min((containerW - xMargins) / origW, (containerH - yMargins) / origH); 352 | scaledW = origW * ratio; 353 | scaledH = origH * ratio; 354 | xScale = ratio; 355 | yScale = ratio; 356 | } else if (childBox.scale == ScaleType.FIT) { 357 | scaledW = containerW - xMargins; 358 | scaledH = containerH - yMargins; 359 | xScale = (containerW - xMargins) / layoutComp.spriteModel.origWidth; 360 | yScale = (containerH - yMargins) / layoutComp.spriteModel.origHeight; 361 | } else if (layoutComp.spriteModel.origWidth > 0 && layoutComp.spriteModel.origHeight > 0) { 362 | scaledW = layoutComp.spriteModel.origWidth; 363 | scaledH = layoutComp.spriteModel.origHeight; 364 | } else { 365 | scaledW = layoutComp.spriteModel.origWidth = c.contentW.m_value > 0 ? c.contentW.m_value : (containerW - xMargins); 366 | scaledH = layoutComp.spriteModel.origHeight = c.contentH.m_value > 0 ? c.contentH.m_value : (containerH - yMargins); 367 | } 368 | solver.addConstraint(c.xScale == xScale); 369 | solver.addConstraint(c.yScale == yScale); 370 | solver.addConstraint(c.contentW == scaledW); 371 | solver.addConstraint(c.contentH == scaledH); 372 | solver.addConstraint(c.containerW == containerW); 373 | solver.addConstraint(c.containerH == containerH); 374 | switch (childBox.hAlign) { 375 | case Align.NEAR: 376 | solver.addConstraint(c.x == c.left); 377 | case Align.MID: 378 | solver.addConstraint(c.x == c.center); 379 | case Align.FAR: 380 | solver.addConstraint(c.x == c.right); 381 | } 382 | switch (childBox.vAlign) { 383 | case Align.NEAR: 384 | solver.addConstraint(c.y >= c.top); 385 | case Align.MID: 386 | solver.addConstraint(c.y == c.middle); 387 | case Align.FAR: 388 | solver.addConstraint(c.y <= c.bottom); 389 | } 390 | solver.updateVariables(); 391 | layoutComp.spriteModel.x = c.x.m_value; 392 | layoutComp.spriteModel.y = c.y.m_value; 393 | layoutComp.spriteModel.width = scaledW; 394 | layoutComp.spriteModel.height = scaledH; 395 | layoutComp.spriteModel.xScale = xScale; 396 | layoutComp.spriteModel.yScale = yScale; 397 | } 398 | 399 | inline function getDocked(dock:Dock):Array { 400 | var a:Array = []; 401 | if (_children != null) { 402 | for (child in _children) { 403 | if (child.layoutBox != null && child.layoutBox.dock == dock) { 404 | a.push(child); 405 | } 406 | } 407 | } 408 | return a; 409 | } 410 | 411 | inline function calculateSize(size:Size, parent:LayoutComponent):Float { 412 | var parentSize = getComponentSize(parent); 413 | 414 | var flow = parent != null && parent.layoutBox != null ? parent.layoutBox.flow : Flow.HORIZONTAL; 415 | var val = 0.0; 416 | if (size.percent) { 417 | var parentDim = flow == Flow.VERTICAL ? parentSize.x : parentSize.y; 418 | val = parentDim * (size.value / 100); 419 | } else { 420 | val = size.value * _appModel.scale; 421 | } 422 | return val; 423 | } 424 | 425 | inline function calculateMargins(margins:Margins, parent:LayoutComponent):Margins { 426 | var parentSize = getComponentSize(parent); 427 | margins = initMargins(margins); 428 | var bm = margins.bottom.value * _appModel.scale; 429 | var tm = margins.top.value * _appModel.scale; 430 | var lm = margins.left.value * _appModel.scale; 431 | var rm = margins.right.value * _appModel.scale; 432 | return { 433 | bottom: {value: margins.bottom.percent ? parentSize.y * (bm / 100) : bm}, 434 | top: {value: margins.top.percent ? parentSize.y * (tm / 100) : tm}, 435 | left: {value: margins.left.percent ? parentSize.x * (lm / 100) : lm}, 436 | right: {value: margins.right.percent ? parentSize.x * (rm / 100) : rm}, 437 | }; 438 | } 439 | 440 | inline function getComponentSize(layout:LayoutComponent):Point { 441 | var w = layout != null && !layout.isRoot ? layout.spriteModel.width : _appModel.stageSize.x; 442 | var h = layout != null && !layout.isRoot ? layout.spriteModel.height : _appModel.stageSize.y; 443 | if (layout != null) { 444 | if (layout.layoutBox.size != null) { 445 | if (layout.layoutBox.flow == Flow.VERTICAL) { 446 | if (layout.layoutBox.size.percent) { 447 | h = (layout.layoutBox.size.value * h) / 100; 448 | } else { 449 | h = layout.layoutBox.size.value * _appModel.scale; 450 | } 451 | } else { 452 | if (layout.layoutBox.size.percent) { 453 | w = (layout.layoutBox.size.value * w) / 100; 454 | } else { 455 | w = layout.layoutBox.size.value * _appModel.scale; 456 | } 457 | } 458 | } 459 | } 460 | 461 | return {x: w, y: h}; 462 | } 463 | 464 | inline function getComponentScale(layout:LayoutComponent):Point { 465 | var x = 1.0; 466 | var y = 1.0; 467 | if (layout != null) { 468 | x = layout.spriteModel.xScale; 469 | y = layout.spriteModel.yScale; 470 | } 471 | return {x: x, y: y}; 472 | } 473 | 474 | inline function calculatePadding() { 475 | var value:Float; 476 | if (layoutBox.padding == null) { 477 | value = 0; 478 | } else if (layoutBox.padding.percent) { 479 | value = layoutBox.size.value * (layoutBox.padding.value / 100); 480 | } else { 481 | value = layoutBox.padding.value * _appModel.scale; 482 | } 483 | _computedPadding = {value: value}; 484 | } 485 | 486 | inline function initMargins(margins:Margins):Margins { 487 | if (margins == null) { 488 | margins = {}; 489 | } 490 | if (margins.left == null) { 491 | margins.left = {value: 0}; 492 | } 493 | if (margins.right == null) { 494 | margins.right = {value: 0}; 495 | } 496 | if (margins.top == null) { 497 | margins.top = {value: 0}; 498 | } 499 | if (margins.bottom == null) { 500 | margins.bottom = {value: 0}; 501 | } 502 | return margins; 503 | } 504 | 505 | inline function onResize(event:InteractionEvent) { 506 | layout(); 507 | } 508 | } 509 | 510 | /** 511 | * Layout definition, used to define layout for a box. 512 | **/ 513 | typedef LayoutBox = { 514 | ?margins:Margins, 515 | ?dock:Dock, 516 | ?flow:Flow, 517 | ?size:Size, 518 | ?scale:ScaleType, 519 | ?padding:Size, 520 | ?vAlign:Align, 521 | ?hAlign:Align, 522 | ?name:String, 523 | ?dimensions:Point, 524 | } 525 | 526 | /** 527 | * Margins definition with sizes for each edge. 528 | **/ 529 | typedef Margins = { 530 | ?bottom:Size, 531 | ?top:Size, 532 | ?left:Size, 533 | ?right:Size, 534 | } 535 | 536 | /** 537 | * Size definition. 538 | **/ 539 | typedef Size = { 540 | value:Float, 541 | ?percent:Bool, 542 | } 543 | 544 | /** 545 | * Constraint variables used to calculate layout 546 | **/ 547 | typedef Constraints = { 548 | x:Variable, 549 | y:Variable, 550 | // Width of container, including margins 551 | containerW:Variable, 552 | // Height of container, including margins 553 | containerH:Variable, 554 | leftMarg:Variable, 555 | rightMarg:Variable, 556 | topMarg:Variable, 557 | bottomMarg:Variable, 558 | left:Variable, 559 | center:Variable, 560 | right:Variable, 561 | top:Variable, 562 | middle:Variable, 563 | bottom:Variable, 564 | // Width of content, excluding margins 565 | contentW:Variable, 566 | // Height of content, excluding margins 567 | contentH:Variable, 568 | ypos:Variable, 569 | xpos:Variable, 570 | yMarg:Variable, 571 | xMarg:Variable, 572 | parentW:Variable, 573 | parentH:Variable, 574 | xScale:Variable, 575 | yScale:Variable, 576 | } 577 | 578 | /** 579 | * Can be either near, mid or far. If flow is horizontal, near is left and far is right. If flow is vertical, near is top and far is bottom. 580 | **/ 581 | enum Align { 582 | NEAR; 583 | MID; 584 | FAR; 585 | } 586 | 587 | /** 588 | * Defines if the container should be docked in the parent. A child container can be docked either top, bottom, left or right. 589 | * Containers that is not docked (ContainerDock .NONE), as well as display object that are not containers, will be layed out 590 | * according to flow and alignment values of the parent. 591 | **/ 592 | typedef Dock = gasm.core.enums.Anchor; 593 | 594 | /** 595 | * Flow defines if children of the container should be laid out vertically or horizontally. 596 | **/ 597 | enum Flow { 598 | VERTICAL; 599 | HORIZONTAL; 600 | } 601 | --------------------------------------------------------------------------------