Browse Source

cross platform typed array proposal

Nicolas Cannasse 10 years ago
parent
commit
dcbfeb652b

+ 63 - 0
std/haxe/io/ArrayBufferView.hx

@@ -0,0 +1,63 @@
+package haxe.io;
+
+typedef ArrayBufferViewData = #if js js.html.ArrayBufferView #else ArrayBufferViewImpl #end
+
+#if !js
+class ArrayBufferViewImpl {
+	public var bytes : haxe.io.Bytes;
+	public var byteOffset : Int;
+	public var byteLength : Int;
+	public function new(bytes, pos, length) {
+		this.bytes = bytes;
+		this.byteOffset = pos;
+		this.byteLength = length;
+	}
+	public function sub( begin : Int, ?length : Int ) {
+		if( length == null ) length = byteLength - begin;
+		if( begin < 0 || length < 0 || begin + length > byteLength ) throw Error.OutsideBounds;
+		return new ArrayBufferViewImpl(bytes, byteOffset + begin, length);
+	}
+}
+#end
+
+abstract ArrayBufferView(ArrayBufferViewData) {
+
+	public var buffer(get,never) : haxe.io.Bytes;
+	public var byteOffset(get, never) : Int;
+	public var byteLength(get, never) : Int;
+
+	public inline function new( size : Int ) {
+		#if js
+		this = new js.html.Uint8Array(size);
+		#else
+		this = new ArrayBufferViewData(haxe.io.Bytes.alloc(size), 0, size);
+		#end
+	}
+
+	inline function get_byteOffset() return this.byteOffset;
+	inline function get_byteLength() return this.byteLength;
+	function get_buffer() : haxe.io.Bytes {
+		#if js
+		return haxe.io.Bytes.ofData(this.buffer);
+		#else
+		return this.bytes;
+		#end
+	}
+
+	public inline function sub( begin : Int, ?length : Int ) {
+		#if js
+		return fromData(new js.html.Uint8Array(this.buffer.slice(begin, length == null ? null : begin+length)));
+		#else
+		return fromData(this.sub(begin,length));
+		#end
+	}
+	
+	public inline function getData() : ArrayBufferViewData {
+		return this;
+	}
+	
+	public static inline function fromData( a : ArrayBufferViewData ) : ArrayBufferView {
+		return cast a;
+	}
+
+}

+ 11 - 1
std/haxe/io/BytesBuffer.hx

@@ -102,6 +102,11 @@ class BytesBuffer {
 		b.Write(src.getData(), 0, src.length);
 		b.Write(src.getData(), 0, src.length);
 		#elseif java
 		#elseif java
 		b.write(src.getData(), 0, src.length);
 		b.write(src.getData(), 0, src.length);
+		#elseif js
+		var b1 = b;
+		var b2 = @:privateAccess src.b;
+		for( i in 0...src.length )
+			b.push(b2[i]);
 		#else
 		#else
 		var b1 = b;
 		var b1 = b;
 		var b2 = src.getData();
 		var b2 = src.getData();
@@ -158,6 +163,11 @@ class BytesBuffer {
 		b.Write(src.getData(), pos, len);
 		b.Write(src.getData(), pos, len);
 		#elseif java
 		#elseif java
 		b.write(src.getData(), pos, len);
 		b.write(src.getData(), pos, len);
+		#elseif js
+		var b1 = b;
+		var b2 = @:privateAccess src.b;
+		for( i in pos...pos+len )
+			b.push(b2[i]);
 		#else
 		#else
 		var b1 = b;
 		var b1 = b;
 		var b2 = src.getData();
 		var b2 = src.getData();
@@ -189,7 +199,7 @@ class BytesBuffer {
 		var buf = python.lib.Builtin.bytearray(b);
 		var buf = python.lib.Builtin.bytearray(b);
 		var bytes = new Bytes(buf.length, buf);
 		var bytes = new Bytes(buf.length, buf);
 		#elseif js
 		#elseif js
-		var bytes = new Bytes(b.length,new BytesData(b));
+		var bytes = new Bytes(new js.html.Uint8Array(b).buffer);
 		#else
 		#else
 		var bytes = new Bytes(b.length,b);
 		var bytes = new Bytes(b.length,b);
 		#end
 		#end

+ 1 - 1
std/haxe/io/BytesData.hx

@@ -37,7 +37,7 @@ package haxe.io;
 #elseif python
 #elseif python
 	typedef BytesData = python.lib.ByteArray;
 	typedef BytesData = python.lib.ByteArray;
 #elseif js
 #elseif js
-	typedef BytesData = js.html.Uint8Array;
+	typedef BytesData = js.html.ArrayBuffer;
 #else
 #else
 	typedef BytesData = Array<Int>;
 	typedef BytesData = Array<Int>;
 #end
 #end

+ 3 - 3
std/haxe/io/BytesInput.hx

@@ -22,7 +22,7 @@
 package haxe.io;
 package haxe.io;
 
 
 class BytesInput extends Input {
 class BytesInput extends Input {
-	var b : BytesData;
+	var b : #if js js.html.Uint8Array #else BytesData #end;
 	#if !flash9
 	#if !flash9
 	var pos : Int;
 	var pos : Int;
 	var len : Int;
 	var len : Int;
@@ -50,7 +50,7 @@ class BytesInput extends Input {
 			this.b = ba;
 			this.b = ba;
 		this.b.endian = flash.utils.Endian.LITTLE_ENDIAN;
 		this.b.endian = flash.utils.Endian.LITTLE_ENDIAN;
 		#else
 		#else
-		this.b = b.getData();
+		this.b = #if js @:privateAccess b.b #else b.getData() #end;
 		this.pos = pos;
 		this.pos = pos;
 		this.len = len;
 		this.len = len;
 		this.totlen = len;
 		this.totlen = len;
@@ -144,7 +144,7 @@ class BytesInput extends Input {
 			untyped __php__("$buf->b = substr($buf->b, 0, $pos) . substr($this->b, $this->pos, $len) . substr($buf->b, $pos+$len)");
 			untyped __php__("$buf->b = substr($buf->b, 0, $pos) . substr($this->b, $this->pos, $len) . substr($buf->b, $pos+$len)");
 			#else
 			#else
 			var b1 = b;
 			var b1 = b;
-			var b2 = buf.getData();
+			var b2 = #if js @:privateAccess buf.b #else buf.getData() #end;
 			for( i in 0...len )
 			for( i in 0...len )
 				b2[pos+i] = b1[this.pos+i];
 				b2[pos+i] = b1[this.pos+i];
 			#end
 			#end

+ 102 - 0
std/haxe/io/Float32Array.hx

@@ -0,0 +1,102 @@
+/*
+ * Copyright (C)2005-2015 Haxe Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+package haxe.io;
+
+typedef Float32ArrayData = #if js js.html.Float32Array #else ArrayBufferView.ArrayBufferViewData #end
+
+abstract Float32Array(Float32ArrayData) {
+
+	public static inline var BYTES_PER_ELEMENT = 4;
+	public var length(get,never) : Int;
+	public var view(get,never) : ArrayBufferView;
+
+	public inline function new( elements : Int ) {
+		#if js
+		this = new Float32ArrayData(elements);
+		#else
+		this = new ArrayBufferView(elements * BYTES_PER_ELEMENT).getData();
+		#end
+	}
+		
+	inline function get_length() {
+		#if js
+		return this.length;
+		#else
+		return this.byteLength >> 2;
+		#end
+	}
+
+	public inline function get_view() : ArrayBufferView {
+		return ArrayBufferView.fromData(this);
+	}
+	
+	@:arrayAccess public inline function get( index : Int ) : Float {
+		#if js
+		return this[index];
+		#else
+		return this.bytes.getFloat((index<<2) + this.byteOffset);
+		#end
+	}
+	
+	@:arrayAccess public inline function set( index : Int, value : Float ) : Float {
+		#if js
+		return this[index] = value;
+		#else
+		if( index >= 0 && index < length ) {
+			this.bytes.setFloat((index<<2) + this.byteOffset, value);
+			return value;
+		}
+		return 0;
+		#end
+	}
+	
+	public inline function sub( begin : Int, ?length : Int ) {
+		#if js
+		return fromData(this.subarray(begin, length == null ? null : begin+length));
+		#else
+		return fromData(this.sub(begin<<2,length<<2));
+		#end
+	}
+	
+	public inline function getData() : Float32ArrayData {
+		return this;
+	}
+	
+	public static function fromData( d : Float32ArrayData ) : Float32Array {
+		return cast d;
+	}
+	
+	public static function fromArray( a : Array<Float>, pos = 0, ?length ) : Float32Array {
+		if( length == null ) length = a.length - pos;
+		if( pos < 0 || length < 0 || pos + length > a.length ) throw Error.OutsideBounds;
+		#if js
+		if( pos == 0 && length == a.length )
+			return fromData(new Float32ArrayData(a));
+		#end
+		var i = new Float32Array(a.length);
+		for( idx in 0...length )
+			i[idx] = a[idx + pos];
+		return i;
+	}
+	
+}
+

+ 1 - 1
std/haxe/io/Input.hx

@@ -60,7 +60,7 @@ class Input {
 	**/
 	**/
 	public function readBytes( s : Bytes, pos : Int, len : Int ) : Int {
 	public function readBytes( s : Bytes, pos : Int, len : Int ) : Int {
 		var k = len;
 		var k = len;
-		var b = s.getData();
+		var b = #if js @:privateAccess s.b #else s.getData() #end;
 		if( pos < 0 || len < 0 || pos + len > s.length )
 		if( pos < 0 || len < 0 || pos + len > s.length )
 			throw Error.OutsideBounds;
 			throw Error.OutsideBounds;
 		while( k > 0 ) {
 		while( k > 0 ) {

+ 102 - 0
std/haxe/io/Uint8Array.hx

@@ -0,0 +1,102 @@
+/*
+ * Copyright (C)2005-2015 Haxe Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+package haxe.io;
+
+typedef Uint8ArrayData = #if js js.html.Uint8Array #else ArrayBufferView.ArrayBufferViewData #end
+
+abstract Uint8Array(Uint8ArrayData) {
+
+	public static inline var BYTES_PER_ELEMENT = 1;
+	public var length(get,never) : Int;
+	public var view(get,never) : ArrayBufferView;
+
+	public inline function new( elements : Int ) {
+		#if js
+		this = new Uint8ArrayData(elements * BYTES_PER_ELEMENT);
+		#else
+		this = new ArrayBufferView(elements * BYTES_PER_ELEMENT).getData();
+		#end
+	}
+	
+	inline function get_length() {
+		#if js
+		return this.length;
+		#else
+		return this.byteLength;
+		#end
+	}
+
+	public inline function get_view() : ArrayBufferView {
+		return ArrayBufferView.fromData(this);
+	}
+
+	@:arrayAccess public inline function get( index : Int ) {
+		#if js
+		return this[index];
+		#else
+		return this.bytes.get(index + this.byteOffset);
+		#end
+	}
+	
+	@:arrayAccess public inline function set( index : Int, value : Int ) : Int {
+		#if js
+		return this[index] = value;
+		#else
+		if( index >= 0 && index < length ) {
+			this.bytes.set(index + this.byteOffset, value);
+			return value;
+		}
+		return 0;
+		#end
+	}
+	
+	public inline function sub( begin : Int, ?length : Int ) {
+		#if js
+		return fromData(this.subarray(begin, length == null ? null : begin+length));
+		#else
+		return fromData(this.sub(begin,length));
+		#end
+	}
+	
+	public inline function getData() : Uint8ArrayData {
+		return this;
+	}
+		
+	public static function fromData( d : Uint8ArrayData ) : Uint8Array {
+		return cast d;
+	}
+	
+	public static function fromArray( a : Array<Int>, pos = 0, ?length ) : Uint8Array {
+		if( length == null ) length = a.length - pos;
+		if( pos < 0 || length < 0 || pos + length > a.length ) throw Error.OutsideBounds;
+		#if js
+		if( pos == 0 && length == a.length )
+			return fromData(new Uint8ArrayData(a));
+		#end
+		var i = new Uint8Array(a.length);
+		for( idx in 0...length )
+			i[idx] = a[idx + pos];
+		return i;
+	}
+	
+}
+

+ 14 - 10
std/js/_std/haxe/io/Bytes.hx

@@ -26,12 +26,13 @@ import js.html.compat.Uint8Array;
 class Bytes {
 class Bytes {
 
 
 	public var length(default,null) : Int;
 	public var length(default,null) : Int;
-	var b : BytesData;
+	var b : js.html.Uint8Array;
 	var data : js.html.DataView;
 	var data : js.html.DataView;
 
 
-	function new(length:Int,b:BytesData) {
-		this.length = length;
-		this.b = b;
+	function new(b:BytesData) {
+		this.length = b.byteLength;
+		this.b = new js.html.Uint8Array(b);
+		untyped b.hxBytes = this;
 	}
 	}
 
 
 	public inline function get( pos : Int ) : Int {
 	public inline function get( pos : Int ) : Int {
@@ -57,7 +58,7 @@ class Bytes {
 
 
 	public function sub( pos : Int, len : Int ) : Bytes {
 	public function sub( pos : Int, len : Int ) : Bytes {
 		if( pos < 0 || len < 0 || pos + len > length ) throw Error.OutsideBounds;
 		if( pos < 0 || len < 0 || pos + len > length ) throw Error.OutsideBounds;
-		return new Bytes(len,new BytesData(b.buffer.slice(pos+b.byteOffset,pos+b.byteOffset+len)));
+		return new Bytes(b.buffer.slice(pos+b.byteOffset,pos+b.byteOffset+len));
 	}
 	}
 
 
 	public function compare( other : Bytes ) : Int {
 	public function compare( other : Bytes ) : Int {
@@ -159,11 +160,11 @@ class Bytes {
 	}
 	}
 
 
 	public inline function getData() : BytesData {
 	public inline function getData() : BytesData {
-		return b;
+		return b.buffer;
 	}
 	}
 
 
 	public static function alloc( length : Int ) : Bytes {
 	public static function alloc( length : Int ) : Bytes {
-		return new Bytes(length,new BytesData(length));
+		return new Bytes(new BytesData(length));
 	}
 	}
 
 
 	public static function ofString( s : String ) : Bytes {
 	public static function ofString( s : String ) : Bytes {
@@ -191,15 +192,18 @@ class Bytes {
 				a.push( 0x80 | (c & 63) );
 				a.push( 0x80 | (c & 63) );
 			}
 			}
 		}
 		}
-		return new Bytes(a.length,new BytesData(a));
+		return new Bytes(new js.html.Uint8Array(a).buffer);
 	}
 	}
 
 
 	public static function ofData( b : BytesData ) : Bytes {
 	public static function ofData( b : BytesData ) : Bytes {
-		return new Bytes(b.length,b);
+		var hb = untyped b.hxBytes;
+		if( hb != null ) return hb;
+		return new Bytes(b);
 	}
 	}
 
 
 	public inline static function fastGet( b : BytesData, pos : Int ) : Int {
 	public inline static function fastGet( b : BytesData, pos : Int ) : Int {
-		return b[pos];
+		// this requires that we have wrapped it with haxe.io.Bytes beforehand
+		return untyped b.bytes[pos];
 	}
 	}
 
 
 }
 }

+ 52 - 0
tests/unit/src/unitstd/haxe/io/Float32Array.unit.hx

@@ -0,0 +1,52 @@
+var b = new haxe.io.Float32Array(5);
+b[0] == 0;
+b[4] == 0;
+b.length == 5;
+
+// check float write
+b[1] = 1.25;
+b[1] == 1.25;
+
+// check loss of precision
+b[1] = 8589934592.;
+b[1] == 8589934592.;
+
+// set
+for( i in 0...5 )
+	b[i] = i + 1;
+b[0] == 1;
+b[4] == 5;
+
+// access outside bounds is unspecified but should not crash
+try b[-1] catch( e : Dynamic ) {};
+try b[5] catch(e : Dynamic) {};
+
+// same for writing
+try b[-1] = 55 catch( e : Dynamic ) {};
+try b[5] = 55 catch(e : Dynamic) {};
+
+var b2 = b.sub(1,3);
+b2[0] == 2;
+b2[2] == 4;
+b2.length == 3;
+
+// check memory sharing
+b2[0] = 0xCC;
+b2[0] == 0xCC;
+b[1] == 0xCC;
+
+// should we allow writing past bounds ?
+try b2[-1] = 0xBB catch( e : Dynamic ) {};
+b[0] == 1;
+
+try b2[3] = 0xBB catch( e : Dynamic ) {};
+b[4] == 5;
+
+
+b.view == b.view; // no alloc
+
+b.view.buffer == b2.view.buffer;
+b.view.byteLength == 20;
+b.view.byteOffset == 0;
+b2.view.byteLength == 12;
+b2.view.byteOffset == 4;

+ 55 - 0
tests/unit/src/unitstd/haxe/io/Uint8Array.unit.hx

@@ -0,0 +1,55 @@
+var b = new haxe.io.Uint8Array(5);
+b[0] == 0;
+b[4] == 0;
+b.length == 5;
+
+// check write mod 255 
+b[0] = 513;
+b[0] == 1;
+b[0] = -2;
+b[0] == 254;
+
+// vheck write for big int
+b[1] = 65535 * 65534 * 65533;
+b[1] == 0xFA;
+
+// set
+for( i in 0...5 )
+	b[i] = i + 1;
+b[0] == 1;
+b[4] == 5;
+
+// access outside bounds is unspecified but should not crash
+try b[-1] catch( e : Dynamic ) {};
+try b[5] catch(e : Dynamic) {};
+
+// same for writing
+try b[-1] = 55 catch( e : Dynamic ) {};
+try b[5] = 55 catch(e : Dynamic) {};
+
+var b2 = b.sub(1,3);
+b2[0] == 2;
+b2[2] == 4;
+b2.length == 3;
+
+// check memory sharing
+b2[0] = 0xCC;
+b2[0] == 0xCC;
+b[1] == 0xCC;
+
+// should we allow writing past bounds ?
+try b2[-1] = 0xBB catch( e : Dynamic ) {};
+b[0] == 1;
+
+try b2[3] = 0xBB catch( e : Dynamic ) {};
+b[4] == 5;
+
+b.view == b.view; // no alloc
+
+b.view.buffer == b2.view.buffer;
+b.view.byteLength == 5;
+b.view.byteOffset == 0;
+b2.view.byteLength == 3;
+b2.view.byteOffset == 1;
+
+