Browse Source

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

Nicolas Cannasse 9 years ago
parent
commit
e84039ff58

+ 1 - 1
h2d/Drawable.hx

@@ -4,7 +4,7 @@ class Drawable extends Sprite {
 
 
 	public var color(default,null) : h3d.Vector;
 	public var color(default,null) : h3d.Vector;
 	public var blendMode : BlendMode;
 	public var blendMode : BlendMode;
-	public var filter : Bool;
+	public var filter : Null<Bool>;
 	public var tileWrap(default, set) : Bool;
 	public var tileWrap(default, set) : Bool;
 	public var colorKey(default, set) : Null<Int>;
 	public var colorKey(default, set) : Null<Int>;
 	public var colorMatrix(get, set) : Null<h3d.Matrix>;
 	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 paddingBottom = 0;
 
 
 	public var isAbsolute = false;
 	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 offsetX = 0;
 	public var offsetY = 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.
 		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 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 minWidth(default, set) : Null<Int>;
 	public var minHeight(default, set) : Null<Int>;
 	public var minHeight(default, set) : Null<Int>;
 	public var maxWidth(default, set) : Null<Int>;
 	public var maxWidth(default, set) : Null<Int>;
 	public var maxHeight(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.
 		Will set all padding values at the same time.
@@ -77,7 +84,7 @@ class Flow extends Sprite {
 	/**
 	/**
 		The horizontal space between two flowed elements.
 		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.
 		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.
 		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 background : h2d.ScaleGrid;
 	var debugGraphics : h2d.Graphics;
 	var debugGraphics : h2d.Graphics;
@@ -155,11 +167,11 @@ class Flow extends Sprite {
 		return isVertical = v;
 		return isVertical = v;
 	}
 	}
 
 
-	function set_halign(v) {
-		if( halign == v )
+	function set_horizontalAlign(v) {
+		if( horizontalAlign == v )
 			return v;
 			return v;
 		needReflow = true;
 		needReflow = true;
-		return halign = v;
+		return horizontalAlign = v;
 	}
 	}
 
 
 	function set_debug(v) {
 	function set_debug(v) {
@@ -176,11 +188,11 @@ class Flow extends Sprite {
 		return debug = v;
 		return debug = v;
 	}
 	}
 
 
-	function set_valign(v) {
-		if( valign == v )
+	function set_verticalAlign(v) {
+		if( verticalAlign == v )
 			return v;
 			return v;
 		needReflow = true;
 		needReflow = true;
-		return valign = v;
+		return verticalAlign = v;
 	}
 	}
 
 
 	function set_overflow(v) {
 	function set_overflow(v) {
@@ -190,6 +202,13 @@ class Flow extends Sprite {
 		return overflow = v;
 		return overflow = v;
 	}
 	}
 
 
+	function set_multiline(v) {
+		if( multiline == v )
+			return v;
+		needReflow = true;
+		return multiline = v;
+	}
+
 	function set_lineHeight(v) {
 	function set_lineHeight(v) {
 		if( lineHeight == v )
 		if( lineHeight == v )
 			return v;
 			return v;
@@ -313,11 +332,11 @@ class Flow extends Sprite {
 		return minHeight = h;
 		return minHeight = h;
 	}
 	}
 
 
-	function set_horitontalSpacing(s) {
-		if( horitontalSpacing == s )
+	function set_horizontalSpacing(s) {
+		if( horizontalSpacing == s )
 			return s;
 			return s;
 		needReflow = true;
 		needReflow = true;
-		return horitontalSpacing = s;
+		return horizontalSpacing = s;
 	}
 	}
 
 
 	function set_verticalSpacing(s) {
 	function set_verticalSpacing(s) {
@@ -396,28 +415,30 @@ class Flow extends Sprite {
 
 
 		var cw, ch;
 		var cw, ch;
 		if( !isVertical ) {
 		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 startX = paddingLeft + borderWidth;
 			var x : Float = startX;
 			var x : Float = startX;
 			var y : Float = paddingTop + borderHeight;
 			var y : Float = paddingTop + borderHeight;
 			cw = x;
 			cw = x;
 			var maxLineHeight = 0.;
 			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 tmpBounds = tmpBounds;
 			var maxWidth = maxWidth == null ? 100000000 : maxWidth - (paddingLeft + paddingRight + borderWidth * 2);
 			var maxWidth = maxWidth == null ? 100000000 : maxWidth - (paddingLeft + paddingRight + borderWidth * 2);
 			var lastIndex = 0;
 			var lastIndex = 0;
 
 
 			inline function alignLine( maxIndex ) {
 			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 ) {
 				for( i in lastIndex...maxIndex ) {
 					var p = properties[i];
 					var p = properties[i];
 					if( p.isAbsolute ) continue;
 					if( p.isAbsolute ) continue;
 					var c = childs[i];
 					var c = childs[i];
 					if( !c.visible ) continue;
 					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;
 					c.y = y + p.offsetY + p.paddingTop;
 					switch( a ) {
 					switch( a ) {
 					case Bottom:
 					case Bottom:
@@ -451,7 +472,7 @@ class Flow extends Sprite {
 				p.isBreak = br;
 				p.isBreak = br;
 				x += p.calculatedWidth;
 				x += p.calculatedWidth;
 				if( x > cw ) cw = x;
 				if( x > cw ) cw = x;
-				x += horitontalSpacing;
+				x += horizontalSpacing;
 				if( p.calculatedHeight > maxLineHeight ) maxLineHeight = p.calculatedHeight;
 				if( p.calculatedHeight > maxLineHeight ) maxLineHeight = p.calculatedHeight;
 			}
 			}
 			alignLine(childs.length);
 			alignLine(childs.length);
@@ -472,7 +493,7 @@ class Flow extends Sprite {
 					midSpace = 0;
 					midSpace = 0;
 				}
 				}
 				var px;
 				var px;
-				var align = p.halign == null ? halign : p.halign;
+				var align = p.horizontalAlign == null ? halign : p.horizontalAlign;
 				switch( align ) {
 				switch( align ) {
 				case Right:
 				case Right:
 					if( midSpace != 0 ) {
 					if( midSpace != 0 ) {
@@ -481,7 +502,7 @@ class Flow extends Sprite {
 					}
 					}
 					xmax -= p.calculatedWidth;
 					xmax -= p.calculatedWidth;
 					px = xmax;
 					px = xmax;
-					xmax -= horitontalSpacing;
+					xmax -= horizontalSpacing;
 				case Middle:
 				case Middle:
 					if( midSpace == 0 ) {
 					if( midSpace == 0 ) {
 						var remSize = p.calculatedWidth;
 						var remSize = p.calculatedWidth;
@@ -489,47 +510,50 @@ class Flow extends Sprite {
 							var p = properties[j];
 							var p = properties[j];
 							if( p.isAbsolute || !childs[j].visible ) continue;
 							if( p.isAbsolute || !childs[j].visible ) continue;
 							if( p.isBreak ) break;
 							if( p.isBreak ) break;
-							remSize += horitontalSpacing + p.calculatedWidth;
+							remSize += horizontalSpacing + p.calculatedWidth;
 						}
 						}
 						midSpace = Std.int(((xmax - xmin) - remSize) * 0.5);
 						midSpace = Std.int(((xmax - xmin) - remSize) * 0.5);
 						xmin += midSpace;
 						xmin += midSpace;
 					}
 					}
 					px = xmin;
 					px = xmin;
-					xmin += p.calculatedWidth + horitontalSpacing;
+					xmin += p.calculatedWidth + horizontalSpacing;
 				default:
 				default:
 					if( midSpace != 0 ) {
 					if( midSpace != 0 ) {
 						xmin += midSpace;
 						xmin += midSpace;
 						midSpace = 0;
 						midSpace = 0;
 					}
 					}
 					px = xmin;
 					px = xmin;
-					xmin += p.calculatedWidth + horitontalSpacing;
+					xmin += p.calculatedWidth + horizontalSpacing;
 				}
 				}
 				childs[i].x = px + p.offsetX + p.paddingLeft;
 				childs[i].x = px + p.offsetX + p.paddingLeft;
 			}
 			}
 
 
 		} else {
 		} 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 startY = paddingTop + borderHeight;
 			var y : Float = startY;
 			var y : Float = startY;
 			var x : Float = paddingLeft + borderWidth;
 			var x : Float = paddingLeft + borderWidth;
 			ch = y;
 			ch = y;
 			var maxColWidth = 0.;
 			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 tmpBounds = tmpBounds;
 			var maxHeight = maxHeight == null ? 100000000 : maxHeight - (paddingTop + paddingBottom + borderHeight * 2);
 			var maxHeight = maxHeight == null ? 100000000 : maxHeight - (paddingTop + paddingBottom + borderHeight * 2);
 			var lastIndex = 0;
 			var lastIndex = 0;
 
 
 			inline function alignLine( maxIndex ) {
 			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 ) {
 				for( i in lastIndex...maxIndex ) {
 					var p = properties[i];
 					var p = properties[i];
 					if( p.isAbsolute ) continue;
 					if( p.isAbsolute ) continue;
 					var c = childs[i];
 					var c = childs[i];
 					if( !c.visible ) continue;
 					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;
 					c.x = x + p.offsetX + p.paddingLeft;
 					switch( a ) {
 					switch( a ) {
 					case Right:
 					case Right:
@@ -560,7 +584,7 @@ class Flow extends Sprite {
 				if( y + p.calculatedHeight > maxHeight && y > startY ) {
 				if( y + p.calculatedHeight > maxHeight && y > startY ) {
 					br = true;
 					br = true;
 					alignLine(i);
 					alignLine(i);
-					x += maxColWidth + horitontalSpacing;
+					x += maxColWidth + horizontalSpacing;
 					maxColWidth = 0;
 					maxColWidth = 0;
 					y = startY;
 					y = startY;
 				}
 				}
@@ -590,7 +614,7 @@ class Flow extends Sprite {
 					midSpace = 0;
 					midSpace = 0;
 				}
 				}
 				var py;
 				var py;
-				var align = p.valign == null ? valign : p.valign;
+				var align = p.verticalAlign == null ? valign : p.verticalAlign;
 				switch( align ) {
 				switch( align ) {
 				case Bottom:
 				case Bottom:
 					if( midSpace != 0 ) {
 					if( midSpace != 0 ) {
@@ -628,7 +652,7 @@ class Flow extends Sprite {
 
 
 		if( minWidth != null && cw < minWidth ) cw = minWidth;
 		if( minWidth != null && cw < minWidth ) cw = minWidth;
 		if( minHeight != null && ch < minHeight ) ch = minHeight;
 		if( minHeight != null && ch < minHeight ) ch = minHeight;
-		if( !overflow ) {
+		if( overflow ) {
 			if( maxWidth != null && cw > maxWidth ) cw = maxWidth;
 			if( maxWidth != null && cw > maxWidth ) cw = maxWidth;
 			if( maxHeight != null && ch > maxHeight ) ch = maxHeight;
 			if( maxHeight != null && ch > maxHeight ) ch = maxHeight;
 		}
 		}

+ 10 - 0
h2d/HtmlText.hx

@@ -138,4 +138,14 @@ class HtmlText extends Text {
 		return c;
 		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 backgroundColor : Null<Int>;
 	public var enableRightButton : Bool;
 	public var enableRightButton : Bool;
 	var scene : Scene;
 	var scene : Scene;
-	var isMouseDown : Int;
+	var mouseDownButton : Int = -1;
+	var parentMask : Mask;
 
 
 	public function new(width, height, ?parent) {
 	public function new(width, height, ?parent) {
 		super(parent);
 		super(parent);
@@ -29,6 +30,7 @@ class Interactive extends Drawable implements hxd.SceneEvents.Interactive {
 	override function onAlloc() {
 	override function onAlloc() {
 		this.scene = getScene();
 		this.scene = getScene();
 		if( scene != null ) scene.addEventTarget(this);
 		if( scene != null ) scene.addEventTarget(this);
+		updateMask();
 		super.onAlloc();
 		super.onAlloc();
 	}
 	}
 
 
@@ -46,6 +48,20 @@ class Interactive extends Drawable implements hxd.SceneEvents.Interactive {
 			scene.removeEventTarget(this);
 			scene.removeEventTarget(this);
 			scene.addEventTarget(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() {
 	override function onDelete() {
@@ -58,23 +74,54 @@ class Interactive extends Drawable implements hxd.SceneEvents.Interactive {
 
 
 	function checkBounds( e : hxd.Event ) {
 	function checkBounds( e : hxd.Event ) {
 		return switch( e.kind ) {
 		return switch( e.kind ) {
-		case EOut, ERelease, EFocus, EFocusLost: false;
+		case EOut, EFocus, EFocusLost: false;
 		default: true;
 		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 {
 	@:noCompletion public function getInteractiveScene() : hxd.SceneEvents.InteractiveScene {
 		return scene;
 		return scene;
 	}
 	}
 
 
 	@:noCompletion public function handleEvent( e : hxd.Event ) {
 	@: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) ) {
 		if( isEllipse && checkBounds(e) ) {
 			var cx = width * 0.5, cy = height * 0.5;
 			var cx = width * 0.5, cy = height * 0.5;
 			var dx = (e.relX - cx) / cx;
 			var dx = (e.relX - cx) / cx;
 			var dy = (e.relY - cy) / cy;
 			var dy = (e.relY - cy) / cy;
 			if( dx * dx + dy * dy > 1 ) {
 			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;
 		if( propagateEvents ) e.propagate = true;
@@ -84,28 +131,28 @@ class Interactive extends Drawable implements hxd.SceneEvents.Interactive {
 			onMove(e);
 			onMove(e);
 		case EPush:
 		case EPush:
 			if( enableRightButton || e.button == 0 ) {
 			if( enableRightButton || e.button == 0 ) {
-				isMouseDown = e.button;
+				mouseDownButton = e.button;
 				onPush(e);
 				onPush(e);
 			}
 			}
 		case ERelease:
 		case ERelease:
 			if( enableRightButton || e.button == 0 ) {
 			if( enableRightButton || e.button == 0 ) {
 				onRelease(e);
 				onRelease(e);
-				if( isMouseDown == e.button )
+				if( mouseDownButton == e.button )
 					onClick(e);
 					onClick(e);
 			}
 			}
-			isMouseDown = -1;
-		case EReleaseNoClick:
+			mouseDownButton = -1;
+		case EReleaseOutside:
 			if( enableRightButton || e.button == 0 ) {
 			if( enableRightButton || e.button == 0 ) {
 				e.kind = ERelease;
 				e.kind = ERelease;
 				onRelease(e);
 				onRelease(e);
-				e.kind = EReleaseNoClick;
+				e.kind = EReleaseOutside;
 			}
 			}
-			isMouseDown = -1;
+			mouseDownButton = -1;
 		case EOver:
 		case EOver:
 			hxd.System.setCursor(cursor);
 			hxd.System.setCursor(cursor);
 			onOver(e);
 			onOver(e);
 		case EOut:
 		case EOut:
-			isMouseDown = -1;
+			mouseDownButton = -1;
 			hxd.System.setCursor(Default);
 			hxd.System.setCursor(Default);
 			onOut(e);
 			onOut(e);
 		case EWheel:
 		case EWheel:

+ 23 - 0
h2d/Mask.hx

@@ -4,6 +4,7 @@ class Mask extends Sprite {
 
 
 	public var width : Int;
 	public var width : Int;
 	public var height : Int;
 	public var height : Int;
+	var parentMask : Mask;
 
 
 	public function new(width, height, ?parent) {
 	public function new(width, height, ?parent) {
 		super(parent);
 		super(parent);
@@ -11,6 +12,28 @@ class Mask extends Sprite {
 		this.height = height;
 		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 ) {
 	override function getBoundsRec( relativeTo, out, forSize ) {
 		super.getBoundsRec(relativeTo, out, forSize);
 		super.getBoundsRec(relativeTo, out, forSize);
 		var xMin = out.xMin, yMin = out.yMin, xMax = out.xMax, yMax = out.yMax;
 		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 bufPos : Int;
 	public var textures : h3d.impl.TextureCache;
 	public var textures : h3d.impl.TextureCache;
 	public var scene : h2d.Scene;
 	public var scene : h2d.Scene;
+	public var defaultFilter : Bool = false;
 
 
 	public var tmpBounds = new h2d.col.Bounds();
 	public var tmpBounds = new h2d.col.Bounds();
 	var texture : h3d.mat.Texture;
 	var texture : h3d.mat.Texture;
@@ -48,9 +49,9 @@ class RenderContext extends h3d.impl.RenderContext {
 		targetsStack = [];
 		targetsStack = [];
 		textures = new h3d.impl.TextureCache();
 		textures = new h3d.impl.TextureCache();
 	}
 	}
-	
+
 	public function dispose() {
 	public function dispose() {
-		textures.dispose();	
+		textures.dispose();
 		if( fixedBuffer != null ) fixedBuffer.dispose();
 		if( fixedBuffer != null ) fixedBuffer.dispose();
 	}
 	}
 
 
@@ -158,7 +159,8 @@ class RenderContext extends h3d.impl.RenderContext {
 	public function beforeDraw() {
 	public function beforeDraw() {
 		if( texture == null ) texture = h3d.mat.Texture.fromColor(0xFF00FF);
 		if( texture == null ) texture = h3d.mat.Texture.fromColor(0xFF00FF);
 		baseShader.texture = texture;
 		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;
 		texture.wrap = currentObj.tileWrap ? Repeat : Clamp;
 		var blend = currentObj.blendMode;
 		var blend = currentObj.blendMode;
 		if( inFilter == currentObj  && blend == Erase ) blend = Add; // add THEN erase
 		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 mouseY(get, null) : Float;
 
 
 	public var zoom(get, set) : Int;
 	public var zoom(get, set) : Int;
+	public var defaultFilter(get, set) : Bool;
 
 
 	var fixedSize : Bool;
 	var fixedSize : Bool;
 	var interactive : Array<Interactive>;
 	var interactive : Array<Interactive>;
@@ -31,6 +32,9 @@ class Scene extends Layers implements h3d.IDrawable implements hxd.SceneEvents.I
 		posChanged = true;
 		posChanged = true;
 	}
 	}
 
 
+	inline function get_defaultFilter() return ctx.defaultFilter;
+	inline function set_defaultFilter(v) return ctx.defaultFilter = v;
+
 	public function setEvents(events) {
 	public function setEvents(events) {
 		this.events = 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.relX = (kx / max) * i.width;
 			event.relY = (ky / max) * i.height;
 			event.relY = (ky / max) * i.height;
-
 			i.handleEvent(event);
 			i.handleEvent(event);
 
 
 			if( event.cancel ) {
 			if( event.cancel ) {
 				event.cancel = false;
 				event.cancel = false;
-				event.propagate = true;
+				event.propagate = false;
 				continue;
 				continue;
 			}
 			}
 
 

+ 13 - 0
h3d/prim/HMDModel.hx

@@ -10,6 +10,7 @@ class HMDModel extends MeshPrimitive {
 	var curMaterial : Int;
 	var curMaterial : Int;
 	var collider : h3d.col.Collider;
 	var collider : h3d.col.Collider;
 	var normalsRecomputed : String;
 	var normalsRecomputed : String;
+	var bufferAliases : Map<String,{ realName : String, offset : Int }> = new Map();
 
 
 	public function new(data, dataPos, lib) {
 	public function new(data, dataPos, lib) {
 		this.data = data;
 		this.data = data;
@@ -37,6 +38,10 @@ class HMDModel extends MeshPrimitive {
 		return lib.getBuffers(data, fmt, defaults, material);
 		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) {
 	override function alloc(engine:h3d.Engine) {
 		dispose();
 		dispose();
 		buffer = new h3d.Buffer(data.vertexCount, data.vertexStride);
 		buffer = new h3d.Buffer(data.vertexCount, data.vertexStride);
@@ -75,6 +80,14 @@ class HMDModel extends MeshPrimitive {
 
 
 		if( normalsRecomputed != null )
 		if( normalsRecomputed != null )
 			recomputeNormals(normalsRecomputed);
 			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 ) {
 	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 propagateEvents : Bool = false;
 	public var enableRightButton : Bool;
 	public var enableRightButton : Bool;
 	var scene : Scene;
 	var scene : Scene;
-	var isMouseDown : Int;
+	var mouseDownButton : Int = -1;
 
 
 	@:allow(h3d.scene.Scene)
 	@:allow(h3d.scene.Scene)
 	var hitPoint = new h3d.Vector();
 	var hitPoint = new h3d.Vector();
@@ -39,6 +39,13 @@ class Interactive extends Object implements hxd.SceneEvents.Interactive {
 		super.onDelete();
 		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 {
 	@:noCompletion public function getInteractiveScene() : hxd.SceneEvents.InteractiveScene {
 		return scene;
 		return scene;
 	}
 	}
@@ -51,28 +58,28 @@ class Interactive extends Object implements hxd.SceneEvents.Interactive {
 			onMove(e);
 			onMove(e);
 		case EPush:
 		case EPush:
 			if( enableRightButton || e.button == 0 ) {
 			if( enableRightButton || e.button == 0 ) {
-				isMouseDown = e.button;
+				mouseDownButton = e.button;
 				onPush(e);
 				onPush(e);
 			}
 			}
 		case ERelease:
 		case ERelease:
 			if( enableRightButton || e.button == 0 ) {
 			if( enableRightButton || e.button == 0 ) {
 				onRelease(e);
 				onRelease(e);
-				if( isMouseDown == e.button )
+				if( mouseDownButton == e.button )
 					onClick(e);
 					onClick(e);
 			}
 			}
-			isMouseDown = -1;
-		case EReleaseNoClick:
+			mouseDownButton = -1;
+		case EReleaseOutside:
 			if( enableRightButton || e.button == 0 ) {
 			if( enableRightButton || e.button == 0 ) {
 				e.kind = ERelease;
 				e.kind = ERelease;
 				onRelease(e);
 				onRelease(e);
-				e.kind = EReleaseNoClick;
+				e.kind = EReleaseOutside;
 			}
 			}
-			isMouseDown = -1;
+			mouseDownButton = -1;
 		case EOver:
 		case EOver:
 			hxd.System.setCursor(cursor);
 			hxd.System.setCursor(cursor);
 			onOver(e);
 			onOver(e);
 		case EOut:
 		case EOut:
-			isMouseDown = -1;
+			mouseDownButton = -1;
 			hxd.System.setCursor(Default);
 			hxd.System.setCursor(Default);
 			onOut(e);
 			onOut(e);
 		case EWheel:
 		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 ) {
 			if( event.cancel ) {
 				event.cancel = false;
 				event.cancel = false;
-				event.propagate = true;
+				event.propagate = false;
 				continue;
 				continue;
 			}
 			}
 
 

+ 3 - 0
h3d/scene/World.hx

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

+ 1 - 1
hxd/Event.hx

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

+ 1 - 1
hxd/SceneEvents.hx

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

+ 5 - 0
hxd/System.hx

@@ -622,8 +622,13 @@ class System {
 		}
 		}
 		win = new sdl.Window("", windowWidth, windowHeight);
 		win = new sdl.Window("", windowWidth, windowHeight);
 		init();
 		init();
+		#if hl
+		sdl.Sdl.defaultEventHandler = onEvent;
+		haxe.MainLoop.add(mainLoop);
+		#else
 		sdl.Sdl.loop(mainLoop,onEvent);
 		sdl.Sdl.loop(mainLoop,onEvent);
 		sdl.Sdl.quit();
 		sdl.Sdl.quit();
+		#end
 	}
 	}
 
 
 	#elseif hl
 	#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> ) {
 	@:from static inline function fromArray<T>( a : Array<T> ) {
+		if( a == null ) return null;
 		return new ArrayProxy(new ArrayProxyData(a));
 		return new ArrayProxy(new ArrayProxyData(a));
 	}
 	}
 }
 }

+ 4 - 2
hxd/net/LocalHost.hx

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

+ 150 - 57
hxd/net/Macros.hx

@@ -24,32 +24,33 @@ enum RpcMode {
 	Server;
 	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 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;
 	Owner;
 }
 }
 
 
-enum PropTypeDesc {
+enum PropTypeDesc<PropType> {
 	PInt;
 	PInt;
 	PFloat;
 	PFloat;
 	PBool;
 	PBool;
 	PString;
 	PString;
 	PBytes;
 	PBytes;
-	PSerializable;
-	PEnum;
+	PSerializable( name : String );
+	PEnum( name : String );
 	PMap( k : PropType, v : PropType );
 	PMap( k : PropType, v : PropType );
 	PArray( k : PropType );
 	PArray( k : PropType );
 	PObj( fields : Array<{ name : String, type : PropType, opt : Bool }> );
 	PObj( fields : Array<{ name : String, type : PropType, opt : Bool }> );
 	PAlias( k : PropType );
 	PAlias( k : PropType );
 	PVector( k : PropType );
 	PVector( k : PropType );
+	PNull( t : PropType );
 	PUnknown;
 	PUnknown;
 }
 }
 
 
 typedef PropType = {
 typedef PropType = {
-	var d : PropTypeDesc;
+	var d : PropTypeDesc<PropType>;
 	var t : ComplexType;
 	var t : ComplexType;
 	@:optional var isProxy : Bool;
 	@:optional var isProxy : Bool;
-	@:optional var isNull : Bool;
 	@:optional var increment : Float;
 	@:optional var increment : Float;
 	@:optional var condSend : Expr;
 	@:optional var condSend : Expr;
 	@:optional var notMutable : Bool;
 	@:optional var notMutable : Bool;
@@ -57,6 +58,8 @@ typedef PropType = {
 
 
 class Macros {
 class Macros {
 
 
+	static var IN_ENUM_SER = false;
+
 	public static macro function serializeValue( ctx : Expr, v : Expr ) : Expr {
 	public static macro function serializeValue( ctx : Expr, v : Expr ) : Expr {
 		var t = Context.typeof(v);
 		var t = Context.typeof(v);
 		var pt = getPropType(t);
 		var pt = getPropType(t);
@@ -64,6 +67,7 @@ class Macros {
 			Context.error("Unsupported serializable type " + t.toString(), v.pos);
 			Context.error("Unsupported serializable type " + t.toString(), v.pos);
 			return macro { };
 			return macro { };
 		}
 		}
+		IN_ENUM_SER = StringTools.startsWith(Context.getLocalClass().toString(), "hxd.net.enumSer.");
 		return serializeExpr(ctx, v, pt);
 		return serializeExpr(ctx, v, pt);
 	}
 	}
 
 
@@ -73,11 +77,40 @@ class Macros {
 		if( pt == null ) {
 		if( pt == null ) {
 			return macro { };
 			return macro { };
 		}
 		}
+		IN_ENUM_SER = StringTools.startsWith(Context.getLocalClass().toString(), "hxd.net.enumSer.");
 		return unserializeExpr(ctx, v, pt);
 		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
 	#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> ) {
 	static function isSerializable( c : Ref<ClassType> ) {
 		while( true ) {
 		while( true ) {
 			var cg = c.get();
 			var cg = c.get();
@@ -114,7 +147,7 @@ class Macros {
 				if( inc == null )
 				if( inc == null )
 					Context.error("Increment requires value parameter", m.pos);
 					Context.error("Increment requires value parameter", m.pos);
 				switch( t.d ) {
 				switch( t.d ) {
-				case PFloat:
+				case PFloat, PNull({ d : PFloat }):
 					t.increment = inc;
 					t.increment = inc;
 				default:
 				default:
 					Context.error("Increment not allowed on " + t.t.toString(), m.pos);
 					Context.error("Increment not allowed on " + t.t.toString(), m.pos);
@@ -137,7 +170,7 @@ class Macros {
 			switch( a.toString() ) {
 			switch( a.toString() ) {
 			case "Float":
 			case "Float":
 				PFloat;
 				PFloat;
-			case "Int":
+			case "Int","UInt":
 				PInt;
 				PInt;
 			case "Bool":
 			case "Bool":
 				PBool;
 				PBool;
@@ -181,8 +214,8 @@ class Macros {
 				if( pt == null ) return null;
 				if( pt == null ) return null;
 				PAlias(pt);
 				PAlias(pt);
 			}
 			}
-		case TEnum(_):
-			PEnum;
+		case TEnum(e,_):
+			PEnum(e.toString());
 		case TAnonymous(a):
 		case TAnonymous(a):
 			var a = a.get();
 			var a = a.get();
 			var fields = [];
 			var fields = [];
@@ -215,7 +248,7 @@ class Macros {
 				throw "assert";
 				throw "assert";
 			default:
 			default:
 				if( isSerializable(c) )
 				if( isSerializable(c) )
-					PSerializable;
+					PSerializable(c.toString());
 				else
 				else
 					return null;
 					return null;
 			}
 			}
@@ -223,10 +256,8 @@ class Macros {
 			switch( td.toString() ) {
 			switch( td.toString() ) {
 			case "Null":
 			case "Null":
 				var p = getPropType(pl[0]);
 				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;
 				return p;
 			default:
 			default:
 				var p = getPropType(Context.follow(t, true));
 				var p = getPropType(Context.follow(t, true));
@@ -248,7 +279,7 @@ class Macros {
 	static function isNullable( t : PropType ) {
 	static function isNullable( t : PropType ) {
 		switch( t.d ) {
 		switch( t.d ) {
 		case PInt, PFloat, PBool:
 		case PInt, PFloat, PBool:
-			return t.isNull;
+			return false;
 		default:
 		default:
 			return true;
 			return true;
 		}
 		}
@@ -263,11 +294,6 @@ class Macros {
 		if( t.isProxy && !skipCheck )
 		if( t.isProxy && !skipCheck )
 			return serializeExpr(ctx, { expr : EField(v, "__value"), pos : v.pos }, t, true);
 			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 ) {
 		switch( t.d ) {
 		case PFloat:
 		case PFloat:
 			return macro $ctx.addFloat($v);
 			return macro $ctx.addFloat($v);
@@ -283,9 +309,12 @@ class Macros {
 			var vk = { expr : EConst(CIdent("k")), pos : v.pos };
 			var vk = { expr : EConst(CIdent("k")), pos : v.pos };
 			var vv = { expr : EConst(CIdent("v")), 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));
 			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;
 			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):
 		case PObj(fields):
 			var nullables = [for( f in fields ) if( isNullable(f.type) ) f];
 			var nullables = [for( f in fields ) if( isNullable(f.type) ) f];
 			var ct = t.t;
 			var ct = t.t;
@@ -326,22 +355,19 @@ class Macros {
 			var at = toProxy(t);
 			var at = toProxy(t);
 			var ve = { expr : EConst(CIdent("e")), pos : v.pos };
 			var ve = { expr : EConst(CIdent("e")), pos : v.pos };
 			return macro $ctx.addVector($v, function(e:$at) return hxd.net.Macros.serializeValue($ctx, $ve));
 			return macro $ctx.addVector($v, function(e:$at) return hxd.net.Macros.serializeValue($ctx, $ve));
-		case PSerializable:
+		case PSerializable(_):
 			return macro $ctx.addKnownRef($v);
 			return macro $ctx.addKnownRef($v);
 		case PAlias(t):
 		case PAlias(t):
 			return serializeExpr(ctx, { expr : ECast(v, null), pos : v.pos }, 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:
 		case PUnknown:
 			throw "assert";
 			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 ) {
 		switch( t.d ) {
 		case PFloat:
 		case PFloat:
 			return macro $v = $ctx.getFloat();
 			return macro $v = $ctx.getFloat();
@@ -361,9 +387,12 @@ class Macros {
 				var v : $vt;
 				var v : $vt;
 				$v = $ctx.getMap(function() { hxd.net.Macros.unserializeValue($ctx, $vk); return $vk; }, function() { hxd.net.Macros.unserializeValue($ctx, $vv); return $vv; });
 				$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;
 			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):
 		case PObj(fields):
 			var nullables = [for( f in fields ) if( isNullable(f.type) ) f];
 			var nullables = [for( f in fields ) if( isNullable(f.type) ) f];
 			if( nullables.length >= 32 )
 			if( nullables.length >= 32 )
@@ -412,7 +441,7 @@ class Macros {
 				var e : $at;
 				var e : $at;
 				$v = $ctx.getVector(function() { hxd.net.Macros.unserializeValue($ctx, e); return e; });
 				$v = $ctx.getVector(function() { hxd.net.Macros.unserializeValue($ctx, e); return e; });
 			};
 			};
-		case PSerializable:
+		case PSerializable(_):
 			function loop(t:ComplexType) {
 			function loop(t:ComplexType) {
 				switch( t ) {
 				switch( t ) {
 				case TPath( { name : "Null", params:[TPType(t)] } ):
 				case TPath( { name : "Null", params:[TPType(t)] } ):
@@ -424,7 +453,7 @@ class Macros {
 				}
 				}
 			}
 			}
 			var cexpr = Context.parse(loop(t.t).toString(), v.pos);
 			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):
 		case PAlias(at):
 			var cvt = at.t;
 			var cvt = at.t;
 			return macro {
 			return macro {
@@ -432,6 +461,9 @@ class Macros {
 				${unserializeExpr(ctx,macro v,at)};
 				${unserializeExpr(ctx,macro v,at)};
 				$v = cast v;
 				$v = cast v;
 			};
 			};
+		case PNull(t):
+			var e = unserializeExpr(ctx, v, t);
+			return macro if( $ctx.getByte() == 0 ) $v = null else $e;
 		case PUnknown:
 		case PUnknown:
 			throw "assert";
 			throw "assert";
 		}
 		}
@@ -482,6 +514,7 @@ class Macros {
 			ul.push(withPos(macro hxd.net.Macros.unserializeValue(__ctx, this.$fname),f.f.pos));
 			ul.push(withPos(macro hxd.net.Macros.unserializeValue(__ctx, this.$fname),f.f.pos));
 		}
 		}
 
 
+		var noCompletion = [{ name : ":noCompletion", pos : pos }];
 		var access = [APublic];
 		var access = [APublic];
 		if( isSubSer )
 		if( isSubSer )
 			access.push(AOverride);
 			access.push(AOverride);
@@ -490,21 +523,21 @@ class Macros {
 				name : "__uid",
 				name : "__uid",
 				pos : pos,
 				pos : pos,
 				access : [APublic],
 				access : [APublic],
-				meta : [{ name : ":noCompletion", pos : pos }],
+				meta : noCompletion,
 				kind : FVar(macro : Int, macro @:privateAccess hxd.net.Serializer.allocUID()),
 				kind : FVar(macro : Int, macro @:privateAccess hxd.net.Serializer.allocUID()),
 			});
 			});
 		fields.push({
 		fields.push({
 			name : "__clid",
 			name : "__clid",
 			pos : pos,
 			pos : pos,
 			access : [AStatic],
 			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})),
 			kind : FVar(macro : Int, macro @:privateAccess hxd.net.Serializer.registerClass($i{cl.name})),
 		});
 		});
 		fields.push({
 		fields.push({
 			name : "getCLID",
 			name : "getCLID",
 			pos : pos,
 			pos : pos,
 			access : access,
 			access : access,
-			meta : [{ name : ":noCompletion", pos : pos }],
+			meta : noCompletion,
 			kind : FFun({ args : [], ret : macro : Int, expr : macro return __clid }),
 			kind : FFun({ args : [], ret : macro : Int, expr : macro return __clid }),
 		});
 		});
 
 
@@ -515,7 +548,6 @@ class Macros {
 			fields.push({
 			fields.push({
 				name : "serialize",
 				name : "serialize",
 				pos : pos,
 				pos : pos,
-				meta : [ { name:":keep", pos:pos } ],
 				access : access,
 				access : access,
 				kind : FFun({
 				kind : FFun({
 					args : [ { name : "__ctx", type : macro : hxd.net.Serializer } ],
 					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} }
 					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 ) {
 		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 )
 			for( f in fields )
 				if( f.name == "unserialize" ) {
 				if( f.name == "unserialize" ) {
@@ -545,7 +605,6 @@ class Macros {
 						f.expr = repl(f.expr);
 						f.expr = repl(f.expr);
 					default:
 					default:
 					}
 					}
-					f.meta.push( { name:":keep", pos:pos } );
 					if( !found ) Context.error("Override of unserialize() with no super.unserialize(ctx) found", f.pos);
 					if( !found ) Context.error("Override of unserialize() with no super.unserialize(ctx) found", f.pos);
 					return fields;
 					return fields;
 				}
 				}
@@ -553,7 +612,6 @@ class Macros {
 			fields.push({
 			fields.push({
 				name : "unserialize",
 				name : "unserialize",
 				pos : pos,
 				pos : pos,
-				meta : [ { name:":keep", pos:pos } ],
 				access : access,
 				access : access,
 				kind : FFun({
 				kind : FFun({
 					args : [ { name : "__ctx", type : macro : hxd.net.Serializer } ],
 					args : [ { name : "__ctx", type : macro : hxd.net.Serializer } ],
@@ -567,8 +625,15 @@ class Macros {
 	}
 	}
 
 
 	public static function buildSerializableEnum() {
 	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 e = e.get();
 			var name = e.pack.length == 0 ? e.name : e.pack.join("_") + "_" + e.name;
 			var name = e.pack.length == 0 ? e.name : e.pack.join("_") + "_" + e.name;
 			try {
 			try {
@@ -593,8 +658,8 @@ class Macros {
 
 
 						var evals = [];
 						var evals = [];
 						for( a in args ) {
 						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 var $aname : $at);
 							evals.push(macro hxd.net.Macros.unserializeValue(ctx,$i{aname}));
 							evals.push(macro hxd.net.Macros.unserializeValue(ctx,$i{aname}));
 						}
 						}
@@ -619,20 +684,19 @@ class Macros {
 					name : name,
 					name : name,
 					pack : ["hxd","net","enumSer"],
 					pack : ["hxd","net","enumSer"],
 					kind : TDClass(),
 					kind : TDClass(),
-					fields : [{
-						name : "serialize",
-						meta : [{name:":extern",pos:pos}],
-						access : [APublic, AInline],
+					fields : [
+					{
+						name : "doSerialize",
+						access : [AStatic],
 						pos : pos,
 						pos : pos,
 						kind : FFun( {
 						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 }},
 							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,
 						pos : pos,
 						kind : FFun( {
 						kind : FFun( {
 							args : [{ name : "ctx", type : macro : hxd.net.Serializer }],
 							args : [{ name : "ctx", type : macro : hxd.net.Serializer }],
@@ -642,12 +706,41 @@ class Macros {
 									return null;
 									return null;
 								return ${{ expr : ESwitch(macro b,ucases,macro throw "Invalid enum index "+b), pos : pos }}
 								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,
 					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);
 				Context.defineType(t);
 				return Context.getType("hxd.net.enumSer." + name);
 				return Context.getType("hxd.net.enumSer." + name);
 			}
 			}
@@ -913,7 +1006,7 @@ class Macros {
 					expr : macro {
 					expr : macro {
 						if( this.$fname != v ) {
 						if( this.$fname != v ) {
 							$markExpr;
 							$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;
 						return v;
 					}
 					}

+ 31 - 33
hxd/net/NetworkHost.hx

@@ -68,8 +68,11 @@ class NetworkClient {
 				o.__host = null;
 				o.__host = null;
 				o.networkRPC(ctx, fid, this);
 				o.networkRPC(ctx, fid, this);
 				o.__host = old;
 				o.__host = old;
-			} else
+			} else {
+				host.rpcClientValue = this;
 				o.networkRPC(ctx, fid, this);
 				o.networkRPC(ctx, fid, this);
+				host.rpcClientValue = null;
+			}
 		case NetworkHost.RPC_WITH_RESULT:
 		case NetworkHost.RPC_WITH_RESULT:
 
 
 			var old = resultID;
 			var old = resultID;
@@ -155,6 +158,11 @@ class NetworkHost {
 
 
 	public var isAuth(default, null) : Bool;
 	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 sendRate : Float = 0.;
 	public var totalSentBytes : Int = 0;
 	public var totalSentBytes : Int = 0;
 
 
@@ -169,6 +177,7 @@ class NetworkHost {
 	var rpcUID = Std.random(0x1000000);
 	var rpcUID = Std.random(0x1000000);
 	var rpcWaits = new Map<Int,Serializer->Void>();
 	var rpcWaits = new Map<Int,Serializer->Void>();
 	var targetClient : NetworkClient;
 	var targetClient : NetworkClient;
+	var rpcClientValue : NetworkClient;
 	var aliveEvents : Array<Void->Void>;
 	var aliveEvents : Array<Void->Void>;
 	public var self(default,null) : NetworkClient;
 	public var self(default,null) : NetworkClient;
 
 
@@ -186,45 +195,25 @@ class NetworkHost {
 
 
 	public function saveState() {
 	public function saveState() {
 		var s = new hxd.net.Serializer();
 		var s = new hxd.net.Serializer();
-		s.begin();
-		var clids = [];
+		s.beginSave();
 		for( r in ctx.refs )
 		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 ) {
 	public function loadSave( bytes : haxe.io.Bytes ) {
 		ctx.refs = new Map();
 		ctx.refs = new Map();
 		@:privateAccess ctx.newObjects = [];
 		@:privateAccess ctx.newObjects = [];
 		ctx.setInput(bytes, 0);
 		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 ) {
 		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) {
 	function mark(o:NetworkSerializable) {
@@ -239,6 +228,10 @@ class NetworkHost {
 		return true;
 		return true;
 	}
 	}
 
 
+	function get_rpcClient() {
+		return rpcClientValue == null ? self : rpcClientValue;
+	}
+
 	public dynamic function onMessage( from : NetworkClient, msg : Dynamic ) {
 	public dynamic function onMessage( from : NetworkClient, msg : Dynamic ) {
 	}
 	}
 
 
@@ -350,10 +343,15 @@ class NetworkHost {
 		return @:privateAccess ctx.newObjects.length == 0 && aliveEvents.length == 0;
 		return @:privateAccess ctx.newObjects.length == 0 && aliveEvents.length == 0;
 	}
 	}
 
 
+	static function sortByUID(o1:Serializable, o2:Serializable) {
+		return o1.__uid - o2.__uid;
+	}
+
 	public function makeAlive() {
 	public function makeAlive() {
 		var objs = @:privateAccess ctx.newObjects;
 		var objs = @:privateAccess ctx.newObjects;
 		if( objs.length == 0 )
 		if( objs.length == 0 )
 			return;
 			return;
+		objs.sort(sortByUID);
 		while( true ) {
 		while( true ) {
 			var o = objs.shift();
 			var o = objs.shift();
 			if( o == null ) break;
 			if( o == null ) break;
@@ -386,7 +384,7 @@ class NetworkHost {
 		ctx.addAnyRef(o);
 		ctx.addAnyRef(o);
 		if( checkEOM ) ctx.addByte(EOM);
 		if( checkEOM ) ctx.addByte(EOM);
 	}
 	}
-	
+
 	function unmark( o : NetworkSerializable ) {
 	function unmark( o : NetworkSerializable ) {
 		if( o.__next == null )
 		if( o.__next == null )
 			return;
 			return;
@@ -413,7 +411,7 @@ class NetworkHost {
 		}
 		}
 		flushProps(); // send changes
 		flushProps(); // send changes
 		o.__host = null;
 		o.__host = null;
-		o.__bits = 0;		
+		o.__bits = 0;
 		unmark(o);
 		unmark(o);
 		if( logger != null )
 		if( logger != null )
 			logger("Unregister " + o+"#"+o.__uid);
 			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 var __uid : Int;
 	public function getCLID() : Int;
 	public function getCLID() : Int;
 	public function serialize( ctx : Serializer ) : Void;
 	public function serialize( ctx : Serializer ) : Void;
+	public function unserializeInit() : Void;
 	public function unserialize( ctx : Serializer ) : Void;
 	public function unserialize( ctx : Serializer ) : Void;
+	public function getSerializeSchema() : Schema;
 }
 }
 
 
 @:genericBuild(hxd.net.Macros.buildSerializableEnum())
 @:genericBuild(hxd.net.Macros.buildSerializableEnum())

+ 397 - 17
hxd/net/Serializer.hx

@@ -1,5 +1,103 @@
 package hxd.net;
 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 {
 class Serializer {
 
 
 	static var UID = 0;
 	static var UID = 0;
@@ -46,7 +144,7 @@ class Serializer {
 			var v = 1;
 			var v = 1;
 			for( i in 0...name.length )
 			for( i in 0...name.length )
 				v = v * 223 + StringTools.fastCodeAt(name,i);
 				v = v * 223 + StringTools.fastCodeAt(name,i);
-			v = 1 + ((v & 0x3FFFFFFF) % 255);
+			v = 1 + ((v & 0x3FFFFFFF) % 65423);
 			return v;
 			return v;
 		}
 		}
 		CLIDS = [for( i in 0...CLASSES.length ) if( subClasses[i].length == 0 && !isSub[i] ) 0 else hash(Type.getClassName(cl[i]))];
 		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 out : haxe.io.BytesBuffer;
 	var input : haxe.io.Bytes;
 	var input : haxe.io.Bytes;
 	var inPos : Int;
 	var inPos : Int;
+	var usedClasses : Array<Bool> = [];
+	var convert : Array<Convert>;
+	var mapIndexes : Array<Int>;
 
 
 	public function new() {
 	public function new() {
 		if( CLIDS == null ) initClassIDS();
 		if( CLIDS == null ) initClassIDS();
@@ -95,7 +196,7 @@ class Serializer {
 	public function unserialize<T:Serializable>( data : haxe.io.Bytes, c : Class<T> ) : T {
 	public function unserialize<T:Serializable>( data : haxe.io.Bytes, c : Class<T> ) : T {
 		refs = new Map();
 		refs = new Map();
 		setInput(data, 0);
 		setInput(data, 0);
-		return getRef(c, Reflect.field(c,"__clid"));
+		return getKnownRef(c);
 	}
 	}
 
 
 	public inline function getByte() {
 	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) {
 	public inline function addFloat(v:Float) {
 		out.addFloat(v);
 		out.addFloat(v);
 	}
 	}
@@ -208,6 +313,12 @@ class Serializer {
 		return v;
 		return v;
 	}
 	}
 
 
+	public inline function getInt32() {
+		var v = input.getInt32(inPos);
+		inPos += 4;
+		return v;
+	}
+
 	public inline function getDouble() {
 	public inline function getDouble() {
 		var v = input.getDouble(inPos);
 		var v = input.getDouble(inPos);
 		inPos += 8;
 		inPos += 8;
@@ -224,8 +335,9 @@ class Serializer {
 		if( s == null )
 		if( s == null )
 			addByte(0);
 			addByte(0);
 		else {
 		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;
 		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 ) {
 		if( s == null ) {
 			addByte(0);
 			addByte(0);
 			return;
 			return;
@@ -267,11 +388,13 @@ class Serializer {
 		if( refs[s.__uid] != null )
 		if( refs[s.__uid] != null )
 			return;
 			return;
 		refs[s.__uid] = s;
 		refs[s.__uid] = s;
-		addInt(s.getCLID());
+		var index = s.getCLID();
+		usedClasses[index] = true;
+		addCLID(index); // index
 		s.serialize(this);
 		s.serialize(this);
 	}
 	}
 
 
-	public inline function addKnownRef( s : Serializable ) {
+	public function addKnownRef( s : Serializable ) {
 		if( s == null ) {
 		if( s == null ) {
 			addByte(0);
 			addByte(0);
 			return;
 			return;
@@ -280,41 +403,56 @@ class Serializer {
 		if( refs[s.__uid] != null )
 		if( refs[s.__uid] != null )
 			return;
 			return;
 		refs[s.__uid] = s;
 		refs[s.__uid] = s;
-		var clid = CLIDS[s.getCLID()];
+		var index = s.getCLID();
+		usedClasses[index] = true;
+		var clid = CLIDS[index];
 		if( clid != 0 )
 		if( clid != 0 )
-			addByte(clid);
+			addCLID(clid); // hash
 		s.serialize(this);
 		s.serialize(this);
 	}
 	}
 
 
-	public inline function getAnyRef() : Serializable {
+	public function getAnyRef() : Serializable {
 		var id = getInt();
 		var id = getInt();
 		if( id == 0 ) return null;
 		if( id == 0 ) return null;
 		if( refs[id] != null )
 		if( refs[id] != null )
 			return cast refs[id];
 			return cast refs[id];
 		var rid = id & SEQ_MASK;
 		var rid = id & SEQ_MASK;
 		if( UID < rid ) UID = rid;
 		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);
 		if( newObjects != null ) newObjects.push(i);
 		i.__uid = id;
 		i.__uid = id;
+		i.unserializeInit();
 		refs[id] = i;
 		refs[id] = i;
-		i.unserialize(this);
+		if( convert != null && convert[clidx] != null )
+			convertRef(i, convert[clidx]);
+		else
+			i.unserialize(this);
 		return i;
 		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();
 		var id = getInt();
 		if( id == 0 ) return null;
 		if( id == 0 ) return null;
 		if( refs[id] != null )
 		if( refs[id] != null )
 			return cast refs[id];
 			return cast refs[id];
 		var rid = id & SEQ_MASK;
 		var rid = id & SEQ_MASK;
 		if( UID < rid ) UID = rid;
 		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);
 		if( newObjects != null ) newObjects.push(i);
 		i.__uid = id;
 		i.__uid = id;
+		i.unserializeInit();
 		refs[id] = i;
 		refs[id] = i;
-		i.unserialize(this);
+		if( convert != null && convert[clidx] != null )
+			convertRef(i, convert[clidx]);
+		else
+			i.unserialize(this);
 		return i;
 		return i;
 	}
 	}
 
 
@@ -322,4 +460,246 @@ class Serializer {
 		return getRef(c, untyped c.__clid);
 		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> ) {
 	@:from static inline function fromVector<T>( a : haxe.ds.Vector<T> ) {
+		if( a == null ) return null;
 		return new VectorProxy(new VectorProxyData(a));
 		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 ) {
 	public static function build( file : String, ?withDict : Bool ) {
 		var path = FileTree.resolvePath();
 		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);
 		Context.registerModuleDependency(Context.getLocalModule(), path + "/" + file);
 		var fields = Context.getBuildFields();
 		var fields = Context.getBuildFields();
 		var pos = Context.currentPos();
 		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);
 		var sampling = format.mp3.Constants.MPEG.srEnum2Num(mp.frames[0].header.samplingRate);
 		#if flash
 		#if flash
 		if( sampling != 44100 )
 		if( sampling != 44100 )
-			samples = Math.ceil(samples * 44100 / sampling);
+			samples = Math.ceil(samples * 44100.0 / sampling);
 		#elseif js
 		#elseif js
 		var ctx = @:privateAccess NativeChannel.getContext();
 		var ctx = @:privateAccess NativeChannel.getContext();
 		samples = Math.ceil(samples * ctx.sampleRate / sampling);
 		samples = Math.ceil(samples * ctx.sampleRate / sampling);
@@ -99,4 +99,4 @@ class Mp3Data extends Data {
 		#end
 		#end
 	}
 	}
 
 
-}
+}

+ 5 - 4
hxsl/GlslOut.hx

@@ -20,11 +20,12 @@ class GlslOut {
 		m.set(ToBool, "bool");
 		m.set(ToBool, "bool");
 		m.set(Texture2D, "_texture2D");
 		m.set(Texture2D, "_texture2D");
 		m.set(LReflect, "reflect");
 		m.set(LReflect, "reflect");
+		m.set(Mat3x4, "_mat3x4");
 		for( g in m )
 		for( g in m )
 			KWDS.set(g, true);
 			KWDS.set(g, true);
 		m;
 		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 buf : StringBuf;
 	var exprIds = 0;
 	var exprIds = 0;
@@ -85,7 +86,7 @@ class GlslOut {
 			add("mat4");
 			add("mat4");
 		case TMat3x4:
 		case TMat3x4:
 			decl(MAT34);
 			decl(MAT34);
-			add("mat3x4");
+			add("_mat3x4");
 		case TSampler2D:
 		case TSampler2D:
 			add("sampler2D");
 			add("sampler2D");
 		case TSamplerCube:
 		case TSamplerCube:
@@ -215,7 +216,7 @@ class GlslOut {
 			switch( [op, e1.t, e2.t] ) {
 			switch( [op, e1.t, e2.t] ) {
 			case [OpMult, TVec(3,VFloat), TMat3x4]:
 			case [OpMult, TVec(3,VFloat), TMat3x4]:
 				decl(MAT34);
 				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(");
 				add("m3x4mult(");
 				addValue(e1, tabs);
 				addValue(e1, tabs);
 				add(",");
 				add(",");
@@ -269,7 +270,7 @@ class GlslOut {
 				add("/*var*/");
 				add("/*var*/");
 			}
 			}
 		case TCall( { e : TGlobal(Mat3) }, [e]) if( e.t == TMat3x4 ):
 		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(");
 			add("_mat3(");
 			addValue(e, tabs);
 			addValue(e, tabs);
 			add(")");
 			add(")");