Преглед изворни кода

Merge branch 'master' of github.com:ncannasse/heaps

Nicolas Cannasse пре 9 година
родитељ
комит
e84039ff58

+ 1 - 1
h2d/Drawable.hx

@@ -4,7 +4,7 @@ class Drawable extends Sprite {
 
 	public var color(default,null) : h3d.Vector;
 	public var blendMode : BlendMode;
-	public var filter : Bool;
+	public var filter : Null<Bool>;
 	public var tileWrap(default, set) : Bool;
 	public var colorKey(default, set) : Null<Int>;
 	public var colorMatrix(get, set) : Null<h3d.Matrix>;

+ 64 - 40
h2d/Flow.hx

@@ -17,8 +17,8 @@ class FlowProperties {
 	public var paddingBottom = 0;
 
 	public var isAbsolute = false;
-	public var halign : Null<FlowAlign>;
-	public var valign : Null<FlowAlign>;
+	public var horizontalAlign : Null<FlowAlign>;
+	public var verticalAlign : Null<FlowAlign>;
 
 	public var offsetX = 0;
 	public var offsetY = 0;
@@ -49,21 +49,28 @@ class Flow extends Sprite {
 		Each change in one of the flow properties or addition/removal of elements will set needReflow to true.
 	**/
 	public var needReflow : Bool = true;
-	public var halign(default,set) : Null<FlowAlign>;
-	public var valign(default,set) : Null<FlowAlign>;
+
+	/**
+		Horizontal alignment of elements inside the flow.
+	**/
+	public var horizontalAlign(default, set) : Null<FlowAlign>;
+
+	/**
+		Vertical alignment of elements inside the flow.
+	**/
+	public var verticalAlign(default,set) : Null<FlowAlign>;
 
 	public var minWidth(default, set) : Null<Int>;
 	public var minHeight(default, set) : Null<Int>;
 	public var maxWidth(default, set) : Null<Int>;
 	public var maxHeight(default, set) : Null<Int>;
+	public var lineHeight(default, set) : Null<Int>;
+	public var colWidth(default, set) : Null<Int>;
 
 	/**
-		Disabling overflow will make a flow report a size of maxWidth/maxHeight even if the elements go past this size.
+		Enabling overflow will treat maxWidth/maxHeight and lineHeight/colWidth constraints as absolute : bigger elements will overflow instead of expanding the limit.
 	**/
-	public var overflow(default, set) : Bool = true;
-
-	public var lineHeight(default, set) : Null<Int>;
-	public var colWidth(default, set) : Null<Int>;
+	public var overflow(default, set) : Bool = false;
 
 	/**
 		Will set all padding values at the same time.
@@ -77,7 +84,7 @@ class Flow extends Sprite {
 	/**
 		The horizontal space between two flowed elements.
 	**/
-	public var horitontalSpacing(default, set) : Int = 0;
+	public var horizontalSpacing(default, set) : Int = 0;
 
 	/**
 		The vertical space between two flowed elements.
@@ -127,7 +134,12 @@ class Flow extends Sprite {
 	/**
 		When set to true, the debug will display red box around the flow, green box for the client space and blue boxes for each element.
 	**/
-	public var debug(default,set) : Bool;
+	public var debug(default, set) : Bool;
+
+	/**
+		When set to true, uses specified lineHeight/colWidth instead of maxWidth/maxHeight for alignment.
+	**/
+	public var multiline(default,set) : Bool = false;
 
 	var background : h2d.ScaleGrid;
 	var debugGraphics : h2d.Graphics;
@@ -155,11 +167,11 @@ class Flow extends Sprite {
 		return isVertical = v;
 	}
 
-	function set_halign(v) {
-		if( halign == v )
+	function set_horizontalAlign(v) {
+		if( horizontalAlign == v )
 			return v;
 		needReflow = true;
-		return halign = v;
+		return horizontalAlign = v;
 	}
 
 	function set_debug(v) {
@@ -176,11 +188,11 @@ class Flow extends Sprite {
 		return debug = v;
 	}
 
-	function set_valign(v) {
-		if( valign == v )
+	function set_verticalAlign(v) {
+		if( verticalAlign == v )
 			return v;
 		needReflow = true;
-		return valign = v;
+		return verticalAlign = v;
 	}
 
 	function set_overflow(v) {
@@ -190,6 +202,13 @@ class Flow extends Sprite {
 		return overflow = v;
 	}
 
+	function set_multiline(v) {
+		if( multiline == v )
+			return v;
+		needReflow = true;
+		return multiline = v;
+	}
+
 	function set_lineHeight(v) {
 		if( lineHeight == v )
 			return v;
@@ -313,11 +332,11 @@ class Flow extends Sprite {
 		return minHeight = h;
 	}
 
-	function set_horitontalSpacing(s) {
-		if( horitontalSpacing == s )
+	function set_horizontalSpacing(s) {
+		if( horizontalSpacing == s )
 			return s;
 		needReflow = true;
-		return horitontalSpacing = s;
+		return horizontalSpacing = s;
 	}
 
 	function set_verticalSpacing(s) {
@@ -396,28 +415,30 @@ class Flow extends Sprite {
 
 		var cw, ch;
 		if( !isVertical ) {
-
-			var halign = halign == null ? Left : halign;
-			var valign = valign == null ? Bottom : valign;
+			var halign = horizontalAlign == null ? Left : horizontalAlign;
+			var valign = verticalAlign == null ? Bottom : verticalAlign;
 
 			var startX = paddingLeft + borderWidth;
 			var x : Float = startX;
 			var y : Float = paddingTop + borderHeight;
 			cw = x;
 			var maxLineHeight = 0.;
-			var minLineHeight = this.lineHeight != null ? lineHeight : (this.minHeight != null && this.maxWidth == null) ? (this.minHeight - (paddingTop + paddingBottom + borderWidth * 2)) : 0;
+			var minLineHeight = this.lineHeight != null ? lineHeight : (this.minHeight != null && !multiline) ? (this.minHeight - (paddingTop + paddingBottom + borderHeight * 2)) : 0;
 			var tmpBounds = tmpBounds;
 			var maxWidth = maxWidth == null ? 100000000 : maxWidth - (paddingLeft + paddingRight + borderWidth * 2);
 			var lastIndex = 0;
 
 			inline function alignLine( maxIndex ) {
-				if( maxLineHeight < minLineHeight ) maxLineHeight = minLineHeight;
+				if( maxLineHeight < minLineHeight )
+					maxLineHeight = minLineHeight;
+				else if( overflow && minLineHeight != 0 )
+					maxLineHeight = minLineHeight;
 				for( i in lastIndex...maxIndex ) {
 					var p = properties[i];
 					if( p.isAbsolute ) continue;
 					var c = childs[i];
 					if( !c.visible ) continue;
-					var a = p.valign != null ? p.valign : valign;
+					var a = p.verticalAlign != null ? p.verticalAlign : valign;
 					c.y = y + p.offsetY + p.paddingTop;
 					switch( a ) {
 					case Bottom:
@@ -451,7 +472,7 @@ class Flow extends Sprite {
 				p.isBreak = br;
 				x += p.calculatedWidth;
 				if( x > cw ) cw = x;
-				x += horitontalSpacing;
+				x += horizontalSpacing;
 				if( p.calculatedHeight > maxLineHeight ) maxLineHeight = p.calculatedHeight;
 			}
 			alignLine(childs.length);
@@ -472,7 +493,7 @@ class Flow extends Sprite {
 					midSpace = 0;
 				}
 				var px;
-				var align = p.halign == null ? halign : p.halign;
+				var align = p.horizontalAlign == null ? halign : p.horizontalAlign;
 				switch( align ) {
 				case Right:
 					if( midSpace != 0 ) {
@@ -481,7 +502,7 @@ class Flow extends Sprite {
 					}
 					xmax -= p.calculatedWidth;
 					px = xmax;
-					xmax -= horitontalSpacing;
+					xmax -= horizontalSpacing;
 				case Middle:
 					if( midSpace == 0 ) {
 						var remSize = p.calculatedWidth;
@@ -489,47 +510,50 @@ class Flow extends Sprite {
 							var p = properties[j];
 							if( p.isAbsolute || !childs[j].visible ) continue;
 							if( p.isBreak ) break;
-							remSize += horitontalSpacing + p.calculatedWidth;
+							remSize += horizontalSpacing + p.calculatedWidth;
 						}
 						midSpace = Std.int(((xmax - xmin) - remSize) * 0.5);
 						xmin += midSpace;
 					}
 					px = xmin;
-					xmin += p.calculatedWidth + horitontalSpacing;
+					xmin += p.calculatedWidth + horizontalSpacing;
 				default:
 					if( midSpace != 0 ) {
 						xmin += midSpace;
 						midSpace = 0;
 					}
 					px = xmin;
-					xmin += p.calculatedWidth + horitontalSpacing;
+					xmin += p.calculatedWidth + horizontalSpacing;
 				}
 				childs[i].x = px + p.offsetX + p.paddingLeft;
 			}
 
 		} else {
 
-			var halign = halign == null ? Left : halign;
-			var valign = valign == null ? Top : valign;
+			var halign = horizontalAlign == null ? Left : horizontalAlign;
+			var valign = verticalAlign == null ? Top : verticalAlign;
 
 			var startY = paddingTop + borderHeight;
 			var y : Float = startY;
 			var x : Float = paddingLeft + borderWidth;
 			ch = y;
 			var maxColWidth = 0.;
-			var minColWidth = this.colWidth != null ? colWidth : (this.minWidth != null && this.maxHeight == null) ? (this.minWidth - (paddingLeft + paddingRight + borderWidth * 2)) : 0;
+			var minColWidth = this.colWidth != null ? colWidth : (this.minWidth != null && !multiline) ? (this.minWidth - (paddingLeft + paddingRight + borderWidth * 2)) : 0;
 			var tmpBounds = tmpBounds;
 			var maxHeight = maxHeight == null ? 100000000 : maxHeight - (paddingTop + paddingBottom + borderHeight * 2);
 			var lastIndex = 0;
 
 			inline function alignLine( maxIndex ) {
-				if( maxColWidth < minColWidth ) maxColWidth = minColWidth;
+				if( maxColWidth < minColWidth )
+					maxColWidth = minColWidth;
+				else if( overflow && minColWidth != 0 )
+					maxColWidth = minColWidth;
 				for( i in lastIndex...maxIndex ) {
 					var p = properties[i];
 					if( p.isAbsolute ) continue;
 					var c = childs[i];
 					if( !c.visible ) continue;
-					var a = p.halign != null ? p.halign : halign;
+					var a = p.horizontalAlign != null ? p.horizontalAlign : halign;
 					c.x = x + p.offsetX + p.paddingLeft;
 					switch( a ) {
 					case Right:
@@ -560,7 +584,7 @@ class Flow extends Sprite {
 				if( y + p.calculatedHeight > maxHeight && y > startY ) {
 					br = true;
 					alignLine(i);
-					x += maxColWidth + horitontalSpacing;
+					x += maxColWidth + horizontalSpacing;
 					maxColWidth = 0;
 					y = startY;
 				}
@@ -590,7 +614,7 @@ class Flow extends Sprite {
 					midSpace = 0;
 				}
 				var py;
-				var align = p.valign == null ? valign : p.valign;
+				var align = p.verticalAlign == null ? valign : p.verticalAlign;
 				switch( align ) {
 				case Bottom:
 					if( midSpace != 0 ) {
@@ -628,7 +652,7 @@ class Flow extends Sprite {
 
 		if( minWidth != null && cw < minWidth ) cw = minWidth;
 		if( minHeight != null && ch < minHeight ) ch = minHeight;
-		if( !overflow ) {
+		if( overflow ) {
 			if( maxWidth != null && cw > maxWidth ) cw = maxWidth;
 			if( maxHeight != null && ch > maxHeight ) ch = maxHeight;
 		}

+ 10 - 0
h2d/HtmlText.hx

@@ -138,4 +138,14 @@ class HtmlText extends Text {
 		return c;
 	}
 
+	override function getBoundsRec( relativeTo : Sprite, out : h2d.col.Bounds, forSize : Bool ) {
+		if( forSize )
+			for( i in images )
+				i.visible = false;
+		super.getBoundsRec(relativeTo, out, forSize);
+		if( forSize )
+			for( i in images )
+				i.visible = true;
+	}
+
 }

+ 58 - 11
h2d/Interactive.hx

@@ -17,7 +17,8 @@ class Interactive extends Drawable implements hxd.SceneEvents.Interactive {
 	public var backgroundColor : Null<Int>;
 	public var enableRightButton : Bool;
 	var scene : Scene;
-	var isMouseDown : Int;
+	var mouseDownButton : Int = -1;
+	var parentMask : Mask;
 
 	public function new(width, height, ?parent) {
 		super(parent);
@@ -29,6 +30,7 @@ class Interactive extends Drawable implements hxd.SceneEvents.Interactive {
 	override function onAlloc() {
 		this.scene = getScene();
 		if( scene != null ) scene.addEventTarget(this);
+		updateMask();
 		super.onAlloc();
 	}
 
@@ -46,6 +48,20 @@ class Interactive extends Drawable implements hxd.SceneEvents.Interactive {
 			scene.removeEventTarget(this);
 			scene.addEventTarget(this);
 		}
+		updateMask();
+	}
+
+	function updateMask() {
+		parentMask = null;
+		var p = parent;
+		while( p != null ) {
+			var m = Std.instance(p, Mask);
+			if( m != null ) {
+				parentMask = m;
+				break;
+			}
+			p = p.parent;
+		}
 	}
 
 	override function onDelete() {
@@ -58,23 +74,54 @@ class Interactive extends Drawable implements hxd.SceneEvents.Interactive {
 
 	function checkBounds( e : hxd.Event ) {
 		return switch( e.kind ) {
-		case EOut, ERelease, EFocus, EFocusLost: false;
+		case EOut, EFocus, EFocusLost: false;
 		default: true;
 		}
 	}
 
+	/**
+		This can be called during or after a push event in order to prevent the release from triggering a click.
+	**/
+	public function preventClick() {
+		mouseDownButton = -1;
+	}
+
 	@:noCompletion public function getInteractiveScene() : hxd.SceneEvents.InteractiveScene {
 		return scene;
 	}
 
 	@:noCompletion public function handleEvent( e : hxd.Event ) {
+		if( parentMask != null && checkBounds(e) ) {
+			var p = parentMask;
+			var pt = new h2d.col.Point(e.relX, e.relY);
+			localToGlobal(pt);
+			var saveX = pt.x, saveY = pt.y;
+			while( p != null ) {
+				pt.x = saveX;
+				pt.y = saveY;
+				var pt = p.globalToLocal(pt);
+				if( pt.x < 0 || pt.y < 0 || pt.x > p.width || pt.y > p.height ) {
+					if( e.kind == ERelease ) {
+						mouseDownButton = -1;
+						break;
+					}
+					e.cancel = true;
+					return;
+				}
+				p = @:privateAccess p.parentMask;
+			}
+		}
 		if( isEllipse && checkBounds(e) ) {
 			var cx = width * 0.5, cy = height * 0.5;
 			var dx = (e.relX - cx) / cx;
 			var dy = (e.relY - cy) / cy;
 			if( dx * dx + dy * dy > 1 ) {
-				e.cancel = true;
-				return;
+				if( e.kind == ERelease )
+					mouseDownButton = -1;
+				else {
+					e.cancel = true;
+					return;
+				}
 			}
 		}
 		if( propagateEvents ) e.propagate = true;
@@ -84,28 +131,28 @@ class Interactive extends Drawable implements hxd.SceneEvents.Interactive {
 			onMove(e);
 		case EPush:
 			if( enableRightButton || e.button == 0 ) {
-				isMouseDown = e.button;
+				mouseDownButton = e.button;
 				onPush(e);
 			}
 		case ERelease:
 			if( enableRightButton || e.button == 0 ) {
 				onRelease(e);
-				if( isMouseDown == e.button )
+				if( mouseDownButton == e.button )
 					onClick(e);
 			}
-			isMouseDown = -1;
-		case EReleaseNoClick:
+			mouseDownButton = -1;
+		case EReleaseOutside:
 			if( enableRightButton || e.button == 0 ) {
 				e.kind = ERelease;
 				onRelease(e);
-				e.kind = EReleaseNoClick;
+				e.kind = EReleaseOutside;
 			}
-			isMouseDown = -1;
+			mouseDownButton = -1;
 		case EOver:
 			hxd.System.setCursor(cursor);
 			onOver(e);
 		case EOut:
-			isMouseDown = -1;
+			mouseDownButton = -1;
 			hxd.System.setCursor(Default);
 			onOut(e);
 		case EWheel:

+ 23 - 0
h2d/Mask.hx

@@ -4,6 +4,7 @@ class Mask extends Sprite {
 
 	public var width : Int;
 	public var height : Int;
+	var parentMask : Mask;
 
 	public function new(width, height, ?parent) {
 		super(parent);
@@ -11,6 +12,28 @@ class Mask extends Sprite {
 		this.height = height;
 	}
 
+	override function onParentChanged() {
+		updateMask();
+	}
+
+	override function onAlloc() {
+		super.onAlloc();
+		updateMask();
+	}
+
+	function updateMask() {
+		parentMask = null;
+		var p = parent;
+		while( p != null ) {
+			var m = Std.instance(p, Mask);
+			if( m != null ) {
+				parentMask = m;
+				break;
+			}
+			p = p.parent;
+		}
+	}
+
 	override function getBoundsRec( relativeTo, out, forSize ) {
 		super.getBoundsRec(relativeTo, out, forSize);
 		var xMin = out.xMin, yMin = out.yMin, xMax = out.xMax, yMax = out.yMax;

+ 5 - 3
h2d/RenderContext.hx

@@ -9,6 +9,7 @@ class RenderContext extends h3d.impl.RenderContext {
 	public var bufPos : Int;
 	public var textures : h3d.impl.TextureCache;
 	public var scene : h2d.Scene;
+	public var defaultFilter : Bool = false;
 
 	public var tmpBounds = new h2d.col.Bounds();
 	var texture : h3d.mat.Texture;
@@ -48,9 +49,9 @@ class RenderContext extends h3d.impl.RenderContext {
 		targetsStack = [];
 		textures = new h3d.impl.TextureCache();
 	}
-	
+
 	public function dispose() {
-		textures.dispose();	
+		textures.dispose();
 		if( fixedBuffer != null ) fixedBuffer.dispose();
 	}
 
@@ -158,7 +159,8 @@ class RenderContext extends h3d.impl.RenderContext {
 	public function beforeDraw() {
 		if( texture == null ) texture = h3d.mat.Texture.fromColor(0xFF00FF);
 		baseShader.texture = texture;
-		texture.filter = currentObj.filter ? Linear : Nearest;
+		var f = currentObj.filter;
+		texture.filter = (currentObj.filter == null ? defaultFilter : currentObj.filter) ? Linear : Nearest;
 		texture.wrap = currentObj.tileWrap ? Repeat : Clamp;
 		var blend = currentObj.blendMode;
 		if( inFilter == currentObj  && blend == Erase ) blend = Add; // add THEN erase

+ 5 - 2
h2d/Scene.hx

@@ -10,6 +10,7 @@ class Scene extends Layers implements h3d.IDrawable implements hxd.SceneEvents.I
 	public var mouseY(get, null) : Float;
 
 	public var zoom(get, set) : Int;
+	public var defaultFilter(get, set) : Bool;
 
 	var fixedSize : Bool;
 	var interactive : Array<Interactive>;
@@ -31,6 +32,9 @@ class Scene extends Layers implements h3d.IDrawable implements hxd.SceneEvents.I
 		posChanged = true;
 	}
 
+	inline function get_defaultFilter() return ctx.defaultFilter;
+	inline function set_defaultFilter(v) return ctx.defaultFilter = v;
+
 	public function setEvents(events) {
 		this.events = events;
 	}
@@ -224,12 +228,11 @@ class Scene extends Layers implements h3d.IDrawable implements hxd.SceneEvents.I
 
 			event.relX = (kx / max) * i.width;
 			event.relY = (ky / max) * i.height;
-
 			i.handleEvent(event);
 
 			if( event.cancel ) {
 				event.cancel = false;
-				event.propagate = true;
+				event.propagate = false;
 				continue;
 			}
 

+ 13 - 0
h3d/prim/HMDModel.hx

@@ -10,6 +10,7 @@ class HMDModel extends MeshPrimitive {
 	var curMaterial : Int;
 	var collider : h3d.col.Collider;
 	var normalsRecomputed : String;
+	var bufferAliases : Map<String,{ realName : String, offset : Int }> = new Map();
 
 	public function new(data, dataPos, lib) {
 		this.data = data;
@@ -37,6 +38,10 @@ class HMDModel extends MeshPrimitive {
 		return lib.getBuffers(data, fmt, defaults, material);
 	}
 
+	public function addAlias( name : String, realName : String, offset = 0 ) {
+		bufferAliases.set(name, {realName : realName, offset : offset });
+	}
+
 	override function alloc(engine:h3d.Engine) {
 		dispose();
 		buffer = new h3d.Buffer(data.vertexCount, data.vertexStride);
@@ -75,6 +80,14 @@ class HMDModel extends MeshPrimitive {
 
 		if( normalsRecomputed != null )
 			recomputeNormals(normalsRecomputed);
+
+		for( name in bufferAliases.keys() ) {
+			var alias = bufferAliases.get(name);
+			var buffer = bufferCache.get(hxsl.Globals.allocID(alias.realName));
+			if( buffer == null ) throw "Buffer " + alias.realName+" not found for alias " + name;
+			if( buffer.offset + alias.offset > buffer.buffer.buffer.stride ) throw "Alias " + name+" for buffer " + alias.realName+" outside stride";
+			addBuffer(name, buffer.buffer, buffer.offset + alias.offset);
+		}
 	}
 
 	public function recomputeNormals( ?name : String ) {

+ 15 - 8
h3d/scene/Interactive.hx

@@ -14,7 +14,7 @@ class Interactive extends Object implements hxd.SceneEvents.Interactive {
 	public var propagateEvents : Bool = false;
 	public var enableRightButton : Bool;
 	var scene : Scene;
-	var isMouseDown : Int;
+	var mouseDownButton : Int = -1;
 
 	@:allow(h3d.scene.Scene)
 	var hitPoint = new h3d.Vector();
@@ -39,6 +39,13 @@ class Interactive extends Object implements hxd.SceneEvents.Interactive {
 		super.onDelete();
 	}
 
+	/**
+		This can be called during or after a push event in order to prevent the release from triggering a click.
+	**/
+	public function preventClick() {
+		mouseDownButton = -1;
+	}
+
 	@:noCompletion public function getInteractiveScene() : hxd.SceneEvents.InteractiveScene {
 		return scene;
 	}
@@ -51,28 +58,28 @@ class Interactive extends Object implements hxd.SceneEvents.Interactive {
 			onMove(e);
 		case EPush:
 			if( enableRightButton || e.button == 0 ) {
-				isMouseDown = e.button;
+				mouseDownButton = e.button;
 				onPush(e);
 			}
 		case ERelease:
 			if( enableRightButton || e.button == 0 ) {
 				onRelease(e);
-				if( isMouseDown == e.button )
+				if( mouseDownButton == e.button )
 					onClick(e);
 			}
-			isMouseDown = -1;
-		case EReleaseNoClick:
+			mouseDownButton = -1;
+		case EReleaseOutside:
 			if( enableRightButton || e.button == 0 ) {
 				e.kind = ERelease;
 				onRelease(e);
-				e.kind = EReleaseNoClick;
+				e.kind = EReleaseOutside;
 			}
-			isMouseDown = -1;
+			mouseDownButton = -1;
 		case EOver:
 			hxd.System.setCursor(cursor);
 			onOver(e);
 		case EOut:
-			isMouseDown = -1;
+			mouseDownButton = -1;
 			hxd.System.setCursor(Default);
 			onOut(e);
 		case EWheel:

+ 1 - 1
h3d/scene/Scene.hx

@@ -125,7 +125,7 @@ class Scene extends Object implements h3d.IDrawable implements hxd.SceneEvents.I
 
 			if( event.cancel ) {
 				event.cancel = false;
-				event.propagate = true;
+				event.propagate = false;
 				continue;
 			}
 

+ 3 - 0
h3d/scene/World.hx

@@ -131,6 +131,8 @@ class World extends Object {
 		while( chunkSize > (1 << chunkBits) )
 			chunkBits++;
 		this.chunkSize = 1 << chunkBits;
+		if( worldSize % chunkSize != 0 )
+			throw "World size must be a multiple of chunk size";
 		this.worldSize = worldSize;
 		this.worldStride = Math.ceil(worldSize / chunkSize);
 		if( autoCollect )
@@ -237,6 +239,7 @@ class World extends Object {
 		}
 	}
 
+	@:noDebug
 	public function loadModel( r : hxd.res.Model ) : WorldModel {
 		var lib = r.toHmd();
 		var models = lib.header.models;

+ 1 - 1
hxd/Event.hx

@@ -11,7 +11,7 @@ enum EventKind {
 	EFocusLost;
 	EKeyDown;
 	EKeyUp;
-	EReleaseNoClick;
+	EReleaseOutside;
 }
 
 class Event {

+ 1 - 1
hxd/SceneEvents.hx

@@ -190,7 +190,7 @@ class SceneEvents {
 				else {
 					var s = i.getInteractiveScene();
 					if( s == null ) continue;
-					event.kind = EReleaseNoClick;
+					event.kind = EReleaseOutside;
 					s.dispatchEvent(event,i);
 					event.kind = ERelease;
 					event.relX = oldX;

+ 5 - 0
hxd/System.hx

@@ -622,8 +622,13 @@ class System {
 		}
 		win = new sdl.Window("", windowWidth, windowHeight);
 		init();
+		#if hl
+		sdl.Sdl.defaultEventHandler = onEvent;
+		haxe.MainLoop.add(mainLoop);
+		#else
 		sdl.Sdl.loop(mainLoop,onEvent);
 		sdl.Sdl.quit();
+		#end
 	}
 
 	#elseif hl

+ 1 - 0
hxd/net/ArrayProxy.hx

@@ -126,6 +126,7 @@ abstract ArrayProxy<T>(ArrayProxyData<T>) {
 	}
 
 	@:from static inline function fromArray<T>( a : Array<T> ) {
+		if( a == null ) return null;
 		return new ArrayProxy(new ArrayProxyData(a));
 	}
 }

+ 4 - 2
hxd/net/LocalHost.hx

@@ -114,7 +114,7 @@ class LocalHost extends NetworkHost {
 		isAuth = true;
 	}
 
-	public static function openNewWindow() {
+	public static function openNewWindow( ?params : Dynamic<String> ) {
 		#if (flash && air3)
 		var opt = new flash.display.NativeWindowInitOptions();
 		opt.renderMode = flash.display.NativeWindowRenderMode.DIRECT;
@@ -126,7 +126,9 @@ class LocalHost extends NetworkHost {
 		var ctx = new flash.system.LoaderContext(false, new flash.system.ApplicationDomain());
 		ctx.allowCodeImport = true;
 		win.stage.addChild(l);
-		l.loadBytes(flash.Lib.current.stage.loaderInfo.bytes, ctx);
+		if( params != null )
+			ctx.parameters = params;
+		l.loadBytes(flash.Lib.current.loaderInfo.bytes, ctx);
 		win.activate();
 		#else
 		throw "Not implemented";

+ 150 - 57
hxd/net/Macros.hx

@@ -24,32 +24,33 @@ enum RpcMode {
 	Server;
 	/*
 		When called on the client: will forward the call to the server if not the owner, or else execute locally.
-		When called on the server: will forward the call to the owner
+		When called on the server: will forward the call to the owner.
+		Will fail if there is no owner.
 	*/
 	Owner;
 }
 
-enum PropTypeDesc {
+enum PropTypeDesc<PropType> {
 	PInt;
 	PFloat;
 	PBool;
 	PString;
 	PBytes;
-	PSerializable;
-	PEnum;
+	PSerializable( name : String );
+	PEnum( name : String );
 	PMap( k : PropType, v : PropType );
 	PArray( k : PropType );
 	PObj( fields : Array<{ name : String, type : PropType, opt : Bool }> );
 	PAlias( k : PropType );
 	PVector( k : PropType );
+	PNull( t : PropType );
 	PUnknown;
 }
 
 typedef PropType = {
-	var d : PropTypeDesc;
+	var d : PropTypeDesc<PropType>;
 	var t : ComplexType;
 	@:optional var isProxy : Bool;
-	@:optional var isNull : Bool;
 	@:optional var increment : Float;
 	@:optional var condSend : Expr;
 	@:optional var notMutable : Bool;
@@ -57,6 +58,8 @@ typedef PropType = {
 
 class Macros {
 
+	static var IN_ENUM_SER = false;
+
 	public static macro function serializeValue( ctx : Expr, v : Expr ) : Expr {
 		var t = Context.typeof(v);
 		var pt = getPropType(t);
@@ -64,6 +67,7 @@ class Macros {
 			Context.error("Unsupported serializable type " + t.toString(), v.pos);
 			return macro { };
 		}
+		IN_ENUM_SER = StringTools.startsWith(Context.getLocalClass().toString(), "hxd.net.enumSer.");
 		return serializeExpr(ctx, v, pt);
 	}
 
@@ -73,11 +77,40 @@ class Macros {
 		if( pt == null ) {
 			return macro { };
 		}
+		IN_ENUM_SER = StringTools.startsWith(Context.getLocalClass().toString(), "hxd.net.enumSer.");
 		return unserializeExpr(ctx, v, pt);
 	}
 
+	public static macro function getFieldType( v : Expr ) {
+		var t = Context.typeof(v);
+		var pt = getPropType(t);
+		if( pt == null )
+			return macro null;
+		var v = toFieldType(pt);
+		return macro $v{v};
+	}
+
 	#if macro
 
+	static function toFieldType( t : PropType ) : Schema.FieldType {
+		return switch( t.d ) {
+		case PInt: PInt;
+		case PFloat: PFloat;
+		case PBool: PBool;
+		case PString: PString;
+		case PBytes: PBytes;
+		case PSerializable(name): PSerializable(name);
+		case PEnum(name): PEnum(name);
+		case PMap(k, v): PMap(toFieldType(k), toFieldType(v));
+		case PArray(v): PArray(toFieldType(v));
+		case PObj(fields): PObj([for( f in fields ) { name : f.name, type : toFieldType(f.type), opt : f.opt }]);
+		case PAlias(t): return toFieldType(t);
+		case PVector(k): PVector(toFieldType(k));
+		case PNull(t): PNull(toFieldType(t));
+		case PUnknown: PUnknown;
+		};
+	}
+
 	static function isSerializable( c : Ref<ClassType> ) {
 		while( true ) {
 			var cg = c.get();
@@ -114,7 +147,7 @@ class Macros {
 				if( inc == null )
 					Context.error("Increment requires value parameter", m.pos);
 				switch( t.d ) {
-				case PFloat:
+				case PFloat, PNull({ d : PFloat }):
 					t.increment = inc;
 				default:
 					Context.error("Increment not allowed on " + t.t.toString(), m.pos);
@@ -137,7 +170,7 @@ class Macros {
 			switch( a.toString() ) {
 			case "Float":
 				PFloat;
-			case "Int":
+			case "Int","UInt":
 				PInt;
 			case "Bool":
 				PBool;
@@ -181,8 +214,8 @@ class Macros {
 				if( pt == null ) return null;
 				PAlias(pt);
 			}
-		case TEnum(_):
-			PEnum;
+		case TEnum(e,_):
+			PEnum(e.toString());
 		case TAnonymous(a):
 			var a = a.get();
 			var fields = [];
@@ -215,7 +248,7 @@ class Macros {
 				throw "assert";
 			default:
 				if( isSerializable(c) )
-					PSerializable;
+					PSerializable(c.toString());
 				else
 					return null;
 			}
@@ -223,10 +256,8 @@ class Macros {
 			switch( td.toString() ) {
 			case "Null":
 				var p = getPropType(pl[0]);
-				if( p != null && !isNullable(p) ) {
-					p.isNull = true;
-					p.t = TPath( { pack : [], name : "Null", params : [TPType(p.t)] } );
-				}
+				if( p != null && !isNullable(p) )
+					p = { d : PNull(p), t : TPath( { pack : [], name : "Null", params : [TPType(p.t)] } ) };
 				return p;
 			default:
 				var p = getPropType(Context.follow(t, true));
@@ -248,7 +279,7 @@ class Macros {
 	static function isNullable( t : PropType ) {
 		switch( t.d ) {
 		case PInt, PFloat, PBool:
-			return t.isNull;
+			return false;
 		default:
 			return true;
 		}
@@ -263,11 +294,6 @@ class Macros {
 		if( t.isProxy && !skipCheck )
 			return serializeExpr(ctx, { expr : EField(v, "__value"), pos : v.pos }, t, true);
 
-		if( t.isNull && !skipCheck ) {
-			var e = serializeExpr(ctx, v, t, true);
-			return macro if( $v == null ) $ctx.addByte(0) else { $ctx.addByte(1); $e; };
-		}
-
 		switch( t.d ) {
 		case PFloat:
 			return macro $ctx.addFloat($v);
@@ -283,9 +309,12 @@ class Macros {
 			var vk = { expr : EConst(CIdent("k")), pos : v.pos };
 			var vv = { expr : EConst(CIdent("v")), pos : v.pos };
 			return macro $ctx.addMap($v, function(k:$kt) return hxd.net.Macros.serializeValue($ctx, $vk), function(v:$vt) return hxd.net.Macros.serializeValue($ctx, $vv));
-		case PEnum:
+		case PEnum(_):
 			var et = t.t;
-			return macro (null : hxd.net.Serializable.SerializableEnum<$et>).serialize($ctx,$v);
+			var ser = "serialize";
+			if( IN_ENUM_SER )
+				ser += "2";
+			return macro (null : hxd.net.Serializable.SerializableEnum<$et>).$ser($ctx,$v);
 		case PObj(fields):
 			var nullables = [for( f in fields ) if( isNullable(f.type) ) f];
 			var ct = t.t;
@@ -326,22 +355,19 @@ class Macros {
 			var at = toProxy(t);
 			var ve = { expr : EConst(CIdent("e")), pos : v.pos };
 			return macro $ctx.addVector($v, function(e:$at) return hxd.net.Macros.serializeValue($ctx, $ve));
-		case PSerializable:
+		case PSerializable(_):
 			return macro $ctx.addKnownRef($v);
 		case PAlias(t):
 			return serializeExpr(ctx, { expr : ECast(v, null), pos : v.pos }, t);
+		case PNull(t):
+			var e = serializeExpr(ctx, v, t);
+			return macro if( $v == null ) $ctx.addByte(0) else { $ctx.addByte(1); $e; };
 		case PUnknown:
 			throw "assert";
 		}
 	}
 
-	static function unserializeExpr( ctx : Expr, v : Expr, t : PropType, skipCheck = false ) {
-
-		if( t.isNull && !skipCheck ) {
-			var e = unserializeExpr(ctx, v, t, true);
-			return macro if( $ctx.getByte() == 0 ) $v = null else $e;
-		}
-
+	static function unserializeExpr( ctx : Expr, v : Expr, t : PropType ) {
 		switch( t.d ) {
 		case PFloat:
 			return macro $v = $ctx.getFloat();
@@ -361,9 +387,12 @@ class Macros {
 				var v : $vt;
 				$v = $ctx.getMap(function() { hxd.net.Macros.unserializeValue($ctx, $vk); return $vk; }, function() { hxd.net.Macros.unserializeValue($ctx, $vv); return $vv; });
 			};
-		case PEnum:
+		case PEnum(_):
 			var et = t.t;
-			return macro { var e : $et; e = (null : hxd.net.Serializable.SerializableEnum<$et>).unserialize($ctx); $v = e; }
+			var unser = "unserialize";
+			if( IN_ENUM_SER )
+				unser += "2";
+			return macro { var __e : $et; __e = (null : hxd.net.Serializable.SerializableEnum<$et>).$unser($ctx); $v = __e; }
 		case PObj(fields):
 			var nullables = [for( f in fields ) if( isNullable(f.type) ) f];
 			if( nullables.length >= 32 )
@@ -412,7 +441,7 @@ class Macros {
 				var e : $at;
 				$v = $ctx.getVector(function() { hxd.net.Macros.unserializeValue($ctx, e); return e; });
 			};
-		case PSerializable:
+		case PSerializable(_):
 			function loop(t:ComplexType) {
 				switch( t ) {
 				case TPath( { name : "Null", params:[TPType(t)] } ):
@@ -424,7 +453,7 @@ class Macros {
 				}
 			}
 			var cexpr = Context.parse(loop(t.t).toString(), v.pos);
-			return macro $v = $ctx.getRef($cexpr,$cexpr.__clid);
+			return macro $v = $ctx.getRef($cexpr,@:privateAccess $cexpr.__clid);
 		case PAlias(at):
 			var cvt = at.t;
 			return macro {
@@ -432,6 +461,9 @@ class Macros {
 				${unserializeExpr(ctx,macro v,at)};
 				$v = cast v;
 			};
+		case PNull(t):
+			var e = unserializeExpr(ctx, v, t);
+			return macro if( $ctx.getByte() == 0 ) $v = null else $e;
 		case PUnknown:
 			throw "assert";
 		}
@@ -482,6 +514,7 @@ class Macros {
 			ul.push(withPos(macro hxd.net.Macros.unserializeValue(__ctx, this.$fname),f.f.pos));
 		}
 
+		var noCompletion = [{ name : ":noCompletion", pos : pos }];
 		var access = [APublic];
 		if( isSubSer )
 			access.push(AOverride);
@@ -490,21 +523,21 @@ class Macros {
 				name : "__uid",
 				pos : pos,
 				access : [APublic],
-				meta : [{ name : ":noCompletion", pos : pos }],
+				meta : noCompletion,
 				kind : FVar(macro : Int, macro @:privateAccess hxd.net.Serializer.allocUID()),
 			});
 		fields.push({
 			name : "__clid",
 			pos : pos,
 			access : [AStatic],
-			meta : [{ name : ":noCompletion", pos : pos }, { name : ":keep", pos : pos }],
+			meta : noCompletion,
 			kind : FVar(macro : Int, macro @:privateAccess hxd.net.Serializer.registerClass($i{cl.name})),
 		});
 		fields.push({
 			name : "getCLID",
 			pos : pos,
 			access : access,
-			meta : [{ name : ":noCompletion", pos : pos }],
+			meta : noCompletion,
 			kind : FFun({ args : [], ret : macro : Int, expr : macro return __clid }),
 		});
 
@@ -515,7 +548,6 @@ class Macros {
 			fields.push({
 				name : "serialize",
 				pos : pos,
-				meta : [ { name:":keep", pos:pos } ],
 				access : access,
 				kind : FFun({
 					args : [ { name : "__ctx", type : macro : hxd.net.Serializer } ],
@@ -523,10 +555,38 @@ class Macros {
 					expr : macro @:privateAccess { ${ if( isSubSer ) macro super.serialize(__ctx) else macro { } }; $b{el} }
 				}),
 			});
+			var schema = [for( s in toSerialize ) {
+				var name = s.f.name;
+				macro { schema.fieldsNames.push($v{name}); schema.fieldsTypes.push(hxd.net.Macros.getFieldType(this.$name)); }
+			}];
+			fields.push({
+				name : "getSerializeSchema",
+				pos : pos,
+				access : access,
+				meta : noCompletion,
+				kind : FFun({
+					args : [],
+					ret : null,
+					expr : macro { var schema = ${if( isSubSer ) macro super.getSerializeSchema() else macro new hxd.net.Schema()}; $b{schema}; return schema; }
+				})
+			});
 		}
 
+		if( fieldsInits.length > 0 || !isSubSer )
+			fields.push({
+				name : "unserializeInit",
+				pos : pos,
+				meta : noCompletion,
+				access : access,
+				kind : FFun({
+					args : [],
+					ret : null,
+					expr : isSubSer ? macro { super.unserializeInit(); $b{fieldsInits}; } : { expr : EBlock(fieldsInits), pos : pos },
+				})
+			});
+
 		if( needUnserialize ) {
-			var unserExpr = macro @:privateAccess { $b{fieldsInits}; ${ if( isSubSer ) macro super.unserialize(__ctx) else macro { } }; $b{ul} };
+			var unserExpr = macro @:privateAccess { ${ if( isSubSer ) macro super.unserialize(__ctx) else macro { } }; $b{ul} };
 
 			for( f in fields )
 				if( f.name == "unserialize" ) {
@@ -545,7 +605,6 @@ class Macros {
 						f.expr = repl(f.expr);
 					default:
 					}
-					f.meta.push( { name:":keep", pos:pos } );
 					if( !found ) Context.error("Override of unserialize() with no super.unserialize(ctx) found", f.pos);
 					return fields;
 				}
@@ -553,7 +612,6 @@ class Macros {
 			fields.push({
 				name : "unserialize",
 				pos : pos,
-				meta : [ { name:":keep", pos:pos } ],
 				access : access,
 				kind : FFun({
 					args : [ { name : "__ctx", type : macro : hxd.net.Serializer } ],
@@ -567,8 +625,15 @@ class Macros {
 	}
 
 	public static function buildSerializableEnum() {
-		switch( Context.getLocalType() ) {
-		case TInst(_, [tenum = TEnum(e, [])]):
+		var pt = switch( Context.getLocalType() ) {
+		case TInst(_, [pt]): pt;
+		default: null;
+		}
+		if( pt != null )
+			pt = Context.follow(pt);
+		if( pt != null )
+		switch( pt ) {
+		case TEnum(e, tparams):
 			var e = e.get();
 			var name = e.pack.length == 0 ? e.name : e.pack.join("_") + "_" + e.name;
 			try {
@@ -593,8 +658,8 @@ class Macros {
 
 						var evals = [];
 						for( a in args ) {
-							var aname = "_"+a.name;
-							var at = a.t.toComplexType();
+							var aname = "_" + a.name;
+							var at = haxe.macro.TypeTools.applyTypeParameters(a.t,e.params,tparams).toComplexType();
 							evals.push(macro var $aname : $at);
 							evals.push(macro hxd.net.Macros.unserializeValue(ctx,$i{aname}));
 						}
@@ -619,20 +684,19 @@ class Macros {
 					name : name,
 					pack : ["hxd","net","enumSer"],
 					kind : TDClass(),
-					fields : [{
-						name : "serialize",
-						meta : [{name:":extern",pos:pos}],
-						access : [APublic, AInline],
+					fields : [
+					{
+						name : "doSerialize",
+						access : [AStatic],
 						pos : pos,
 						kind : FFun( {
-							args : [{ name : "ctx", type : macro : hxd.net.Serializer },{ name : "v", type : tenum.toComplexType() }],
+							args : [{ name : "ctx", type : macro : hxd.net.Serializer },{ name : "v", type : pt.toComplexType() }],
 							expr : macro @:privateAccess if( v == null ) ctx.addByte(0) else ${{ expr : ESwitch(macro v,cases,null), pos : pos }},
-							ret : null,
+							ret : macro : Void,
 						}),
 					},{
-						name : "unserialize",
-						access : [APublic, AInline],
-						meta : [{name:":extern",pos:pos}],
+						name : "doUnserialize",
+						access : [AStatic],
 						pos : pos,
 						kind : FFun( {
 							args : [{ name : "ctx", type : macro : hxd.net.Serializer }],
@@ -642,12 +706,41 @@ class Macros {
 									return null;
 								return ${{ expr : ESwitch(macro b,ucases,macro throw "Invalid enum index "+b), pos : pos }}
 							},
-							ret : tenum.toComplexType(),
+							ret : pt.toComplexType(),
 						}),
 
+					},{
+						name : "serialize",
+						access : [AInline, APublic],
+						meta : [{name:":extern",pos:pos}],
+						pos : pos,
+						kind : FFun( {
+							args : [{ name : "ctx", type : macro : hxd.net.Serializer },{ name : "v", type : pt.toComplexType() }],
+							expr : macro doSerialize(ctx,v),
+							ret : null,
+						}),
+					},{
+						name : "unserialize",
+						access : [AInline, APublic],
+						meta : [{name:":extern",pos:pos}],
+						pos : pos,
+						kind : FFun( {
+							args : [{ name : "ctx", type : macro : hxd.net.Serializer }],
+							expr : macro return doUnserialize(ctx),
+							ret : null,
+						}),
 					}],
 					pos : pos,
 				};
+
+				// hack to allow recursion (duplicate serialize/unserialize for recursive usage)
+				var tf = Reflect.copy(t.fields[t.fields.length - 2]);
+				tf.name += "2";
+				t.fields.push(tf);
+				var tf = Reflect.copy(t.fields[t.fields.length - 2]);
+				tf.name += "2";
+				t.fields.push(tf);
+
 				Context.defineType(t);
 				return Context.getType("hxd.net.enumSer." + name);
 			}
@@ -913,7 +1006,7 @@ class Macros {
 					expr : macro {
 						if( this.$fname != v ) {
 							$markExpr;
-							${if( ftype.isProxy ) macro v.bindHost(this,$v{bitID}) else macro {}};
+							${if( ftype.isProxy ) macro (if( v != null ) v.bindHost(this,$v{bitID})) else macro {}};
 						}
 						return v;
 					}

+ 31 - 33
hxd/net/NetworkHost.hx

@@ -68,8 +68,11 @@ class NetworkClient {
 				o.__host = null;
 				o.networkRPC(ctx, fid, this);
 				o.__host = old;
-			} else
+			} else {
+				host.rpcClientValue = this;
 				o.networkRPC(ctx, fid, this);
+				host.rpcClientValue = null;
+			}
 		case NetworkHost.RPC_WITH_RESULT:
 
 			var old = resultID;
@@ -155,6 +158,11 @@ class NetworkHost {
 
 	public var isAuth(default, null) : Bool;
 
+	/**
+		When a RPC of type Server is performed, this will tell the originating client from the RPC.
+	**/
+	public var rpcClient(get, never) : NetworkClient;
+
 	public var sendRate : Float = 0.;
 	public var totalSentBytes : Int = 0;
 
@@ -169,6 +177,7 @@ class NetworkHost {
 	var rpcUID = Std.random(0x1000000);
 	var rpcWaits = new Map<Int,Serializer->Void>();
 	var targetClient : NetworkClient;
+	var rpcClientValue : NetworkClient;
 	var aliveEvents : Array<Void->Void>;
 	public var self(default,null) : NetworkClient;
 
@@ -186,45 +195,25 @@ class NetworkHost {
 
 	public function saveState() {
 		var s = new hxd.net.Serializer();
-		s.begin();
-		var clids = [];
+		s.beginSave();
 		for( r in ctx.refs )
-			if( !s.refs.exists(r.__uid) ) {
-				var cl = r.getCLID();
-				var cval = Type.getClass(r);
-				s.addInt(cl);
-				if( !clids[cl] ) {
-					clids[cl] = true;
-					s.addString(Type.getClassName(cval));
-				}
-				s.addKnownRef(r);
-				s.addByte(EOM);
-			}
-		s.addInt(-1);
-		return s.end();
+			if( !s.refs.exists(r.__uid) )
+				s.addAnyRef(r);
+		s.addAnyRef(null);
+		return s.endSave();
 	}
 
 	public function loadSave( bytes : haxe.io.Bytes ) {
 		ctx.refs = new Map();
 		@:privateAccess ctx.newObjects = [];
 		ctx.setInput(bytes, 0);
-		var classByName = new Map();
-		for( c in @:privateAccess Serializer.CLASSES )
-			classByName.set(Type.getClassName(c), c);
-		var clids = [];
+		ctx.beginLoadSave();
 		while( true ) {
-			var cl = ctx.getInt();
-			if( cl < 0 ) break;
-			var cval = clids[cl];
-			if( cval == null ) {
-				var cname = ctx.getString();
-				cval = classByName.get(cname);
-				if( cval == null ) throw "Unsupported class " + cname;
-				clids[cl] = cval;
-			}
-			ctx.getKnownRef(cval);
-			if( ctx.getByte() != EOM ) throw "Save file is not compatible with current version";
+			var v = ctx.getAnyRef();
+			if( v == null ) break;
 		}
+		ctx.endLoadSave();
+		ctx.setInput(null, 0);
 	}
 
 	function mark(o:NetworkSerializable) {
@@ -239,6 +228,10 @@ class NetworkHost {
 		return true;
 	}
 
+	function get_rpcClient() {
+		return rpcClientValue == null ? self : rpcClientValue;
+	}
+
 	public dynamic function onMessage( from : NetworkClient, msg : Dynamic ) {
 	}
 
@@ -350,10 +343,15 @@ class NetworkHost {
 		return @:privateAccess ctx.newObjects.length == 0 && aliveEvents.length == 0;
 	}
 
+	static function sortByUID(o1:Serializable, o2:Serializable) {
+		return o1.__uid - o2.__uid;
+	}
+
 	public function makeAlive() {
 		var objs = @:privateAccess ctx.newObjects;
 		if( objs.length == 0 )
 			return;
+		objs.sort(sortByUID);
 		while( true ) {
 			var o = objs.shift();
 			if( o == null ) break;
@@ -386,7 +384,7 @@ class NetworkHost {
 		ctx.addAnyRef(o);
 		if( checkEOM ) ctx.addByte(EOM);
 	}
-	
+
 	function unmark( o : NetworkSerializable ) {
 		if( o.__next == null )
 			return;
@@ -413,7 +411,7 @@ class NetworkHost {
 		}
 		flushProps(); // send changes
 		o.__host = null;
-		o.__bits = 0;		
+		o.__bits = 0;
 		unmark(o);
 		if( logger != null )
 			logger("Unregister " + o+"#"+o.__uid);

+ 29 - 0
hxd/net/Schema.hx

@@ -0,0 +1,29 @@
+package hxd.net;
+
+typedef FieldType = Macros.PropTypeDesc<FieldType>;
+
+#if !macro
+class Schema implements Serializable {
+
+	public var checkSum(get,never) : Int;
+	@:s @:notMutable public var fieldsNames : Array<String>;
+	@:s @:notMutable public var fieldsTypes : Array<FieldType>;
+
+	public function new() {
+		fieldsNames = [];
+		fieldsTypes = [];
+	}
+
+	function get_checkSum() {
+		var s = new Serializer();
+		s.begin();
+		var old = __uid;
+		__uid = 0;
+		s.addKnownRef(this);
+		__uid = old;
+		var bytes = s.end();
+		return haxe.crypto.Crc32.make(bytes);
+	}
+
+}
+#end

+ 2 - 0
hxd/net/Serializable.hx

@@ -5,7 +5,9 @@ interface Serializable {
 	public var __uid : Int;
 	public function getCLID() : Int;
 	public function serialize( ctx : Serializer ) : Void;
+	public function unserializeInit() : Void;
 	public function unserialize( ctx : Serializer ) : Void;
+	public function getSerializeSchema() : Schema;
 }
 
 @:genericBuild(hxd.net.Macros.buildSerializableEnum())

+ 397 - 17
hxd/net/Serializer.hx

@@ -1,5 +1,103 @@
 package hxd.net;
 
+class ConvertField {
+	public var index : Int;
+	public var same : Bool;
+	public var defaultValue : Dynamic;
+	public var from : Null<Schema.FieldType>;
+	public var to : Null<Schema.FieldType>;
+	public function new(from, to) {
+		this.from = from;
+		this.to = to;
+	}
+}
+
+class Convert {
+
+	public var read : Array<ConvertField>;
+	public var write : Array<ConvertField>;
+
+	public function new( ourSchema : Schema, schema : Schema ) {
+		var ourMap = new Map();
+		for( i in 0...ourSchema.fieldsNames.length )
+			ourMap.set(ourSchema.fieldsNames[i], ourSchema.fieldsTypes[i]);
+		read = [];
+
+		var map = new Map();
+		for( i in 0...schema.fieldsNames.length ) {
+			var oldT = schema.fieldsTypes[i];
+			var newT = ourMap.get(schema.fieldsNames[i]);
+			var c = new ConvertField(oldT, newT);
+			if( newT != null ) {
+				if( sameType(oldT, newT) )
+					c.same = true;
+				else
+					c.defaultValue = getDefault(newT);
+			}
+			c.index = read.length;
+			read.push(c);
+			map.set(schema.fieldsNames[i], c);
+		}
+
+		write = [];
+		for( i in 0...ourSchema.fieldsNames.length ) {
+			var newT = ourSchema.fieldsTypes[i];
+			var c = map.get(ourSchema.fieldsNames[i]);
+			if( c == null ) {
+				c = new ConvertField(null, newT);
+				// resolve default value using a specific method ?
+				c.defaultValue = getDefault(newT);
+			}
+			write.push(c);
+		}
+	}
+
+	static function sameType( a : Schema.FieldType, b : Schema.FieldType ) {
+		switch( [a, b] ) {
+		case [PMap(ak, av), PMap(bk, bv)]:
+			return sameType(ak, bk) && sameType(av, bv);
+		case [PArray(a), PArray(b)],[PVector(a),PVector(b)],[PNull(a),PNull(b)]:
+			return sameType(a, b);
+		case [PObj(fa), PObj(fb)]:
+			if( fa.length != fb.length ) return false;
+			for( i in 0...fa.length ) {
+				var a = fa[i];
+				var b = fb[i];
+				if( a.name != b.name || a.opt != b.opt || !sameType(a.type, b.type) )
+					return false;
+			}
+			return true;
+		case [PAlias(a), PAlias(b)]:
+			return sameType(a, b);
+		case [PAlias(a), _]:
+			return sameType(a, b);
+		case [_, PAlias(b)]:
+			return sameType(a, b);
+		default:
+			return Type.enumEq(a, b);
+		}
+	}
+
+	function getDefault(t:Schema.FieldType) : Dynamic {
+		return switch( t ) {
+		case PInt: 0;
+		case PFloat: 0.;
+		case PArray(_): [];
+		case PMap(k, _):
+			switch( k ) {
+			case PInt: new Map<Int,Dynamic>();
+			case PString: new Map<String,Dynamic>();
+			default: new Map<{},Dynamic>();
+			}
+		case PVector(_): new haxe.ds.Vector<Dynamic>(0);
+		case PBool: false;
+		case PAlias(t): getDefault(t);
+		case PEnum(_), PNull(_), PObj(_), PSerializable(_), PString, PUnknown, PBytes: null;
+		};
+	}
+
+}
+
 class Serializer {
 
 	static var UID = 0;
@@ -46,7 +144,7 @@ class Serializer {
 			var v = 1;
 			for( i in 0...name.length )
 				v = v * 223 + StringTools.fastCodeAt(name,i);
-			v = 1 + ((v & 0x3FFFFFFF) % 255);
+			v = 1 + ((v & 0x3FFFFFFF) % 65423);
 			return v;
 		}
 		CLIDS = [for( i in 0...CLASSES.length ) if( subClasses[i].length == 0 && !isSub[i] ) 0 else hash(Type.getClassName(cl[i]))];
@@ -64,6 +162,9 @@ class Serializer {
 	var out : haxe.io.BytesBuffer;
 	var input : haxe.io.Bytes;
 	var inPos : Int;
+	var usedClasses : Array<Bool> = [];
+	var convert : Array<Convert>;
+	var mapIndexes : Array<Int>;
 
 	public function new() {
 		if( CLIDS == null ) initClassIDS();
@@ -95,7 +196,7 @@ class Serializer {
 	public function unserialize<T:Serializable>( data : haxe.io.Bytes, c : Class<T> ) : T {
 		refs = new Map();
 		setInput(data, 0);
-		return getRef(c, Reflect.field(c,"__clid"));
+		return getKnownRef(c);
 	}
 
 	public inline function getByte() {
@@ -115,6 +216,10 @@ class Serializer {
 		}
 	}
 
+	public inline function addInt32(v:Int) {
+		out.addInt32(v);
+	}
+
 	public inline function addFloat(v:Float) {
 		out.addFloat(v);
 	}
@@ -208,6 +313,12 @@ class Serializer {
 		return v;
 	}
 
+	public inline function getInt32() {
+		var v = input.getInt32(inPos);
+		inPos += 4;
+		return v;
+	}
+
 	public inline function getDouble() {
 		var v = input.getDouble(inPos);
 		inPos += 8;
@@ -224,8 +335,9 @@ class Serializer {
 		if( s == null )
 			addByte(0);
 		else {
-			addInt(s.length + 1);
-			out.addString(s);
+			var b = haxe.io.Bytes.ofString(s);
+			addInt(b.length + 1);
+			out.add(b);
 		}
 	}
 
@@ -258,7 +370,16 @@ class Serializer {
 		return s;
 	}
 
-	public inline function addAnyRef( s : Serializable ) {
+	public inline function addCLID( clid : Int ) {
+		addByte(clid >> 8);
+		addByte(clid & 0xFF);
+	}
+
+	public inline function getCLID() {
+		return (getByte() << 8) | getByte();
+	}
+
+	public function addAnyRef( s : Serializable ) {
 		if( s == null ) {
 			addByte(0);
 			return;
@@ -267,11 +388,13 @@ class Serializer {
 		if( refs[s.__uid] != null )
 			return;
 		refs[s.__uid] = s;
-		addInt(s.getCLID());
+		var index = s.getCLID();
+		usedClasses[index] = true;
+		addCLID(index); // index
 		s.serialize(this);
 	}
 
-	public inline function addKnownRef( s : Serializable ) {
+	public function addKnownRef( s : Serializable ) {
 		if( s == null ) {
 			addByte(0);
 			return;
@@ -280,41 +403,56 @@ class Serializer {
 		if( refs[s.__uid] != null )
 			return;
 		refs[s.__uid] = s;
-		var clid = CLIDS[s.getCLID()];
+		var index = s.getCLID();
+		usedClasses[index] = true;
+		var clid = CLIDS[index];
 		if( clid != 0 )
-			addByte(clid);
+			addCLID(clid); // hash
 		s.serialize(this);
 	}
 
-	public inline function getAnyRef() : Serializable {
+	public function getAnyRef() : Serializable {
 		var id = getInt();
 		if( id == 0 ) return null;
 		if( refs[id] != null )
 			return cast refs[id];
 		var rid = id & SEQ_MASK;
 		if( UID < rid ) UID = rid;
-		var clid = getInt();
-		var i : Serializable = Type.createEmptyInstance(CLASSES[clid]);
+		var clidx = getCLID();
+		if( mapIndexes != null ) clidx = mapIndexes[clidx];
+		var i : Serializable = Type.createEmptyInstance(CLASSES[clidx]);
 		if( newObjects != null ) newObjects.push(i);
 		i.__uid = id;
+		i.unserializeInit();
 		refs[id] = i;
-		i.unserialize(this);
+		if( convert != null && convert[clidx] != null )
+			convertRef(i, convert[clidx]);
+		else
+			i.unserialize(this);
 		return i;
 	}
 
-	public inline function getRef<T:Serializable>( c : Class<T>, clid : Int ) : T {
+	public function getRef<T:Serializable>( c : Class<T>, clidx : Int ) : T {
 		var id = getInt();
 		if( id == 0 ) return null;
 		if( refs[id] != null )
 			return cast refs[id];
 		var rid = id & SEQ_MASK;
 		if( UID < rid ) UID = rid;
-		var clid = CLIDS[clid];
-		var i = Type.createEmptyInstance(clid == 0 ? c : cast CL_BYID[getByte()]);
+		if( CLIDS[clidx] != 0 ) {
+			var realIdx = getCLID();
+			c = cast CL_BYID[realIdx];
+			if( convert != null ) clidx = untyped c.__clid; // real class convert
+		}
+		var i : T = Type.createEmptyInstance(c);
 		if( newObjects != null ) newObjects.push(i);
 		i.__uid = id;
+		i.unserializeInit();
 		refs[id] = i;
-		i.unserialize(this);
+		if( convert != null && convert[clidx] != null )
+			convertRef(i, convert[clidx]);
+		else
+			i.unserialize(this);
 		return i;
 	}
 
@@ -322,4 +460,246 @@ class Serializer {
 		return getRef(c, untyped c.__clid);
 	}
 
+
+	public function beginSave() {
+		begin();
+		usedClasses = [];
+	}
+
+	public function endSave() {
+		var content = end();
+		begin();
+		var classes = [];
+		var schemas = [];
+		var sidx = CLASSES.indexOf(Schema);
+		for( i in 0...usedClasses.length ) {
+			if( !usedClasses[i] || i == sidx ) continue;
+			var c = CLASSES[i];
+			var schema = (Type.createEmptyInstance(c) : Serializable).getSerializeSchema();
+			schemas.push(schema);
+			classes.push(i);
+			addKnownRef(schema);
+			refs.remove(schema.__uid);
+		}
+		var schemaData = end();
+		begin();
+		addString("HXS");
+		addByte(1);
+		for( i in 0...classes.length ) {
+			var index = classes[i];
+			addString(Type.getClassName(CLASSES[index]));
+			addCLID(index);
+			addInt32(schemas[i].checkSum);
+		}
+		addString(null);
+		addInt(schemaData.length);
+		out.add(schemaData);
+		out.add(content);
+		return end();
+	}
+
+	public function beginLoadSave() {
+		var classByName = new Map();
+		var schemas = [];
+		var mapIndexes = [];
+		var indexes = [];
+		var needConvert = false;
+		var needReindex = false;
+		for( i in 0...CLASSES.length ) {
+			classByName.set(Type.getClassName(CLASSES[i]), i);
+			mapIndexes[i] = i;
+		}
+		if( getString() != "HXS" )
+			throw "Invalid HXS data";
+		var version = getByte();
+		if( version != 1 )
+			throw "Unsupported HXS version " + version;
+
+		/*
+			TODO : one last thing not checked is the fact that we can save some data in addKnownRef
+			if the class has no subclass.
+			We need to save this status in our class schema and adapt if it gets changed.
+		*/
+
+		while( true ) {
+			var clname = getString();
+			if( clname == null ) break;
+			var index = getCLID();
+			var crc = getInt32();
+			var ourClassIndex = classByName.get(clname);
+			if( ourClassIndex == null ) throw "Missing class " + clname+" found in HXS data";
+			var ourSchema = (Type.createEmptyInstance(CLASSES[ourClassIndex]) : Serializable).getSerializeSchema();
+			if( ourSchema.checkSum != crc ) {
+				needConvert = true;
+				schemas[index] = ourSchema;
+			}
+			if( index != ourClassIndex ) {
+				needReindex = true;
+				mapIndexes[index] = ourClassIndex;
+			}
+			indexes.push(index);
+		}
+		var schemaDataSize = getInt();
+		if( needConvert ) {
+			convert = [];
+			for( index in indexes ) {
+				var ourSchema = schemas[index];
+				var schema = getKnownRef(Schema);
+				refs.remove(schema.__uid);
+				if( ourSchema != null )
+					convert[mapIndexes[index]] = new Convert(ourSchema, schema);
+			}
+		} else {
+			// skip schema data
+			inPos += schemaDataSize;
+		}
+		if( needReindex )
+			this.mapIndexes = mapIndexes;
+	}
+
+	public function endLoadSave() {
+		convert = null;
+		mapIndexes = null;
+	}
+
+	function convertRef( i : Serializable, c : Convert ) {
+		var values = new haxe.ds.Vector<Dynamic>(c.read.length);
+		var writePos = 0;
+		for( r in c.read )
+			values[r.index] = readValue(r.from);
+		var oldOut = this.out;
+		out = new haxe.io.BytesBuffer();
+		for( w in c.write ) {
+			var v : Dynamic;
+			if( w.from == null )
+				v = w.defaultValue;
+			else {
+				v = values[w.index];
+				if( !w.same ) {
+					if( v == null )
+						v = w.defaultValue;
+					else
+						v = convertValue(v, w.from, w.to);
+				}
+			}
+			writeValue(v, w.to);
+		}
+		var bytes = out.getBytes();
+		out = oldOut;
+		var oldIn = input;
+		var oldPos = inPos;
+		setInput(bytes, 0);
+		i.unserialize(this);
+		setInput(oldIn, oldPos);
+	}
+
+	function isNullable( t : Schema.FieldType ) {
+		return switch( t ) {
+		case PInt, PFloat, PBool: false;
+		default: true;
+		}
+	}
+
+	function convertValue( v : Dynamic, from : Schema.FieldType, to : Schema.FieldType ) : Dynamic {
+		throw "Cannot convert " + v + " from " + from + " to " + to;
+	}
+
+	function readValue( t : Schema.FieldType ) : Dynamic {
+		return switch( t ) {
+		case PInt: getInt();
+		case PFloat: getFloat();
+		case PAlias(t): readValue(t);
+		case PBool: getBool();
+		case PString: getString();
+		case PArray(t): getArray(function() return readValue(t));
+		case PVector(t): getVector(function() return readValue(t));
+		case PBytes: getBytes();
+		case PEnum(name):
+			var ser = "hxd.net.enumSer." + name.split(".").join("_");
+			if( ser == null ) throw "No enum unserializer found for " + name;
+			return (Type.resolveClass(ser) : Dynamic).doUnserialize(this);
+		case PSerializable(name): getKnownRef(Type.resolveClass(name));
+		case PNull(t): getByte() == 0 ? null : readValue(t);
+		case PObj(fields):
+			var bits = getByte();
+			if( bits == 0 )
+				return null;
+			var o = {};
+			bits--;
+			var nullables = [for( f in fields ) if( isNullable(f.type) ) f];
+			for( f in fields ) {
+				var nidx = nullables.indexOf(f);
+				if( nidx >= 0 && bits & (1 << nidx) == 0 ) continue;
+				Reflect.setField(o, f.name, readValue(f.type));
+			}
+			return o;
+		case PMap(k, v):
+			switch( k ) {
+			case PInt:
+				(getMap(function() return readValue(k), function() return readValue(v)) : Map<Int,Dynamic>);
+			case PString:
+				(getMap(function() return readValue(k), function() return readValue(v)) : Map<String,Dynamic>);
+			default:
+				(getMap(function() return readValue(k), function() return readValue(v)) : Map<{},Dynamic>);
+			}
+		case PUnknown:
+			throw "assert";
+		}
+	}
+
+	function writeValue( v : Dynamic, t : Schema.FieldType )  {
+		switch( t ) {
+		case PInt:
+			addInt(v);
+		case PFloat:
+			addFloat(v);
+		case PAlias(t):
+			writeValue(v,t);
+		case PBool:
+			addBool(v);
+		case PString:
+			addString(v);
+		case PArray(t):
+			addArray(v, function(v) return writeValue(v,t));
+		case PVector(t):
+			addVector(v, function(v) return writeValue(v,t));
+		case PBytes:
+			addBytes(v);
+		case PEnum(name):
+			var ser = "hxd.net.enumSer." + name.split(".").join("_");
+			if( ser == null ) throw "No enum unserializer found for " + name;
+			(Type.resolveClass(ser) : Dynamic).doSerialize(this,v);
+		case PSerializable(_):
+			addKnownRef(v);
+		case PNull(t):
+			if( v == null ) {
+				addByte(0);
+			} else {
+				addByte(1);
+				writeValue(v, t);
+			}
+		case PObj(fields):
+			if( v == null )
+				addByte(0);
+			else {
+				var fbits = 0;
+				var nullables = [for( f in fields ) if( isNullable(f.type) ) f];
+				for( i in 0...nullables.length )
+					if( Reflect.field(v, nullables[i].name) != null )
+						fbits |= 1 << i;
+				addByte(fbits + 1);
+				for( f in fields ) {
+					var nidx = nullables.indexOf(f);
+					var name = f.name;
+					if( nidx >= 0 && fbits & (1 << nidx) == 0 ) continue;
+					writeValue(Reflect.field(v, f.name), f.type);
+				}
+			}
+		case PMap(k, t):
+			addMap(v,function(v) writeValue(v,k), function(v) writeValue(v,t));
+		case PUnknown:
+			throw "assert";
+		}
+	}
+
 }

+ 1 - 0
hxd/net/VectorProxy.hx

@@ -55,6 +55,7 @@ abstract VectorProxy<T>(VectorProxyData<T>) {
 	}
 
 	@:from static inline function fromVector<T>( a : haxe.ds.Vector<T> ) {
+		if( a == null ) return null;
 		return new VectorProxy(new VectorProxyData(a));
 	}
 }

+ 8 - 1
hxd/res/DynamicText.hx

@@ -112,7 +112,14 @@ class DynamicText {
 
 	public static function build( file : String, ?withDict : Bool ) {
 		var path = FileTree.resolvePath();
-		var x = Xml.parse(sys.io.File.getContent(path + "/" + file));
+		var fullPath = path + "/" + file;
+		var x = null;
+		try {
+			x = Xml.parse(sys.io.File.getContent(fullPath));
+		} catch( e : haxe.xml.Parser.XmlParserException ) {
+			Context.error(e.message, Context.makePosition({min:e.position, max:e.position, file:fullPath}));
+			return null;
+		}
 		Context.registerModuleDependency(Context.getLocalModule(), path + "/" + file);
 		var fields = Context.getBuildFields();
 		var pos = Context.currentPos();

+ 2 - 2
hxd/snd/Mp3Data.hx

@@ -31,7 +31,7 @@ class Mp3Data extends Data {
 		var sampling = format.mp3.Constants.MPEG.srEnum2Num(mp.frames[0].header.samplingRate);
 		#if flash
 		if( sampling != 44100 )
-			samples = Math.ceil(samples * 44100 / sampling);
+			samples = Math.ceil(samples * 44100.0 / sampling);
 		#elseif js
 		var ctx = @:privateAccess NativeChannel.getContext();
 		samples = Math.ceil(samples * ctx.sampleRate / sampling);
@@ -99,4 +99,4 @@ class Mp3Data extends Data {
 		#end
 	}
 
-}
+}

+ 5 - 4
hxsl/GlslOut.hx

@@ -20,11 +20,12 @@ class GlslOut {
 		m.set(ToBool, "bool");
 		m.set(Texture2D, "_texture2D");
 		m.set(LReflect, "reflect");
+		m.set(Mat3x4, "_mat3x4");
 		for( g in m )
 			KWDS.set(g, true);
 		m;
 	};
-	static var MAT34 = "struct mat3x4 { vec4 a; vec4 b; vec4 c; };";
+	static var MAT34 = "struct _mat3x4 { vec4 a; vec4 b; vec4 c; };";
 
 	var buf : StringBuf;
 	var exprIds = 0;
@@ -85,7 +86,7 @@ class GlslOut {
 			add("mat4");
 		case TMat3x4:
 			decl(MAT34);
-			add("mat3x4");
+			add("_mat3x4");
 		case TSampler2D:
 			add("sampler2D");
 		case TSamplerCube:
@@ -215,7 +216,7 @@ class GlslOut {
 			switch( [op, e1.t, e2.t] ) {
 			case [OpMult, TVec(3,VFloat), TMat3x4]:
 				decl(MAT34);
-				decl("vec3 m3x4mult( vec3 v, mat3x4 m) { vec4 ve = vec4(v,1.0); return vec3(dot(m.a,ve),dot(m.b,ve),dot(m.c,ve)); }");
+				decl("vec3 m3x4mult( vec3 v, _mat3x4 m) { vec4 ve = vec4(v,1.0); return vec3(dot(m.a,ve),dot(m.b,ve),dot(m.c,ve)); }");
 				add("m3x4mult(");
 				addValue(e1, tabs);
 				add(",");
@@ -269,7 +270,7 @@ class GlslOut {
 				add("/*var*/");
 			}
 		case TCall( { e : TGlobal(Mat3) }, [e]) if( e.t == TMat3x4 ):
-			decl("mat3 _mat3( mat3x4 v ) { return mat3(v.a.xyz,v.b.xyz,v.c.xyz); }");
+			decl("mat3 _mat3( _mat3x4 v ) { return mat3(v.a.xyz,v.b.xyz,v.c.xyz); }");
 			add("_mat3(");
 			addValue(e, tabs);
 			add(")");