Browse Source

Merge pull request #1022 from Kelimion/buffer_convert

Add `bytes.buffer_create_of_type` and `bytes.buffer_convert_to_type`.
gingerBill 4 years ago
parent
commit
a2d5f660ed
1 changed files with 181 additions and 0 deletions
  1. 181 0
      core/bytes/util.odin

+ 181 - 0
core/bytes/util.odin

@@ -0,0 +1,181 @@
+package bytes
+
+import "core:intrinsics"
+import "core:mem"
+
+/*
+	Buffer type helpers
+*/
+
+need_endian_conversion :: proc($FT: typeid, $TT: typeid) -> (res: bool) {
+
+	// true if platform endian
+	f: bool;
+	t: bool;
+
+	when ODIN_ENDIAN == "little" {
+		f = intrinsics.type_is_endian_platform(FT) || intrinsics.type_is_endian_little(FT);
+		t = intrinsics.type_is_endian_platform(TT) || intrinsics.type_is_endian_little(TT);
+
+		return f != t;
+	} else {
+		f = intrinsics.type_is_endian_platform(FT) || intrinsics.type_is_endian_big(FT);
+		t = intrinsics.type_is_endian_platform(TT) || intrinsics.type_is_endian_big(TT);
+
+		return f != t;
+	}
+
+	return;
+}
+
+/*
+	Input:
+		count:         number of elements
+		$TT:           destination type
+		$FT:           source type
+		from_buffer:   buffer to convert
+		force_convert: cast each element separately
+
+	Output:
+		res:           Converted/created buffer of []TT.
+		backing:       ^bytes.Buffer{} backing the converted data.
+		alloc:         Buffer was freshly allocated because we couldn't convert in-place. Points to `from_buffer` if `false`.
+		err:           True if we passed too few elements or allocation failed, etc.
+
+	If `from_buffer` is empty, the input type $FT is ignored and `create_buffer_of_type` is called to create a fresh buffer.
+
+	This helper will try to do as little work as possible, so if you're converting between two equally sized types,
+	and they have compatible endianness, the contents will simply be reinterpreted using `slice_data_cast`.
+
+	If you want each element to be converted in this case, set `force_convert` to `true`.
+
+	For example, converting `[]u8{0, 60}` from `[]f16` to `[]u16` will return `[15360]` when simply reinterpreted,
+	and `[1]` if force converted.
+
+	Should you for example want to promote `[]f16` to `[]f32` (or truncate `[]f32` to `[]f16`), the size of these elements
+	being different will result in a conversion anyway, so this flag is unnecessary in cases like these.
+
+	Example:
+		fmt.println("Convert []f16le (x2) to []f32 (x2).");
+		b := []u8{0, 60, 0, 60}; // == []f16{1.0, 1.0}
+
+		res, backing, had_to_allocate, err := bytes.buffer_convert_to_type(2, f32, f16le, b);
+		fmt.printf("res      : %v\n", res);              // [1.000, 1.000]
+		fmt.printf("backing  : %v\n", backing);          // &Buffer{buf = [0, 0, 128, 63, 0, 0, 128, 63], off = 0, last_read = Invalid}
+		fmt.printf("allocated: %v\n", had_to_allocate);  // true
+		fmt.printf("err      : %v\n", err);              // false
+
+		if had_to_allocate { defer bytes.buffer_destroy(backing); }
+
+		fmt.println("\nConvert []f16le (x2) to []u16 (x2).");
+
+		res2: []u16;
+		res2, backing, had_to_allocate, err = bytes.buffer_convert_to_type(2, u16, f16le, b);
+		fmt.printf("res      : %v\n", res2);             // [15360, 15360]
+		fmt.printf("backing  : %v\n", backing);          // Buffer.buf points to `b` because it could be converted in-place.
+		fmt.printf("allocated: %v\n", had_to_allocate);  // false
+		fmt.printf("err      : %v\n", err);              // false
+
+		if had_to_allocate { defer bytes.buffer_destroy(backing); }
+
+		fmt.println("\nConvert []f16le (x2) to []u16 (x2), force_convert=true.");
+
+		res2, backing, had_to_allocate, err = bytes.buffer_convert_to_type(2, u16, f16le, b, true);
+		fmt.printf("res      : %v\n", res2);             // [1, 1]
+		fmt.printf("backing  : %v\n", backing);          // Buffer.buf points to `b` because it could be converted in-place.
+		fmt.printf("allocated: %v\n", had_to_allocate);  // false
+		fmt.printf("err      : %v\n", err);              // false
+
+		if had_to_allocate { defer bytes.buffer_destroy(backing); }
+*/
+buffer_convert_to_type :: proc(count: int, $TT: typeid, $FT: typeid, from_buffer: []u8, force_convert := false) -> (
+	res: []TT, backing: ^Buffer, alloc: bool, err: bool) {
+
+	backing = new(Buffer);
+
+	if len(from_buffer) > 0 {
+		/*
+			Check if we've been given enough input elements.
+		*/
+		from := mem.slice_data_cast([]FT, from_buffer);
+		if len(from) != count {
+			err = true;
+			return;
+		}
+
+		/*
+			We can early out if the types are exactly identical.
+			This needs to be `when`, or res = from will fail if the types are different.
+		*/
+		when FT == TT {
+			res = from;
+			buffer_init(backing, from_buffer);
+			return;
+		}
+
+		/*
+			We can do a data cast if in-size == out-size and no endian conversion is needed.
+		*/
+		convert := need_endian_conversion(FT, TT);
+		convert |= (size_of(TT) * count != len(from_buffer));
+		convert |= force_convert;
+
+		if !convert {
+			// It's just a data cast
+			res = mem.slice_data_cast([]TT, from_buffer);
+			buffer_init(backing, from_buffer);
+
+			if len(res) != count {
+				err = true;
+			}
+			return;
+		} else {
+			if size_of(TT) * count == len(from_buffer) {
+				/*
+					Same size, can do an in-place Endianness conversion.
+					If `force_convert`, this also handles the per-element cast instead of slice_data_cast.
+				*/
+				res  = mem.slice_data_cast([]TT, from_buffer);
+				buffer_init(backing, from_buffer);
+				for v, i in from {
+					res[i] = TT(v);
+				}
+			} else {
+				/*
+					Result is a different size, we need to allocate an output buffer.
+				*/
+				size := size_of(TT) * count;
+				buffer_init_allocator(backing, size, size, context.allocator);
+				alloc = true;
+				res   = mem.slice_data_cast([]TT, backing.buf[:]);
+				if len(res) != count {
+					err = true;
+					return;
+				}
+
+				for v, i in from {
+					res[i] = TT(v);
+				}
+			}
+		}
+	} else {
+		/*
+			The input buffer is empty, so we'll have to create a new one for []TT of length count.
+		*/
+		res, backing, err = buffer_create_of_type(count, TT);
+		alloc = true;
+	}
+
+	return;
+}
+
+buffer_create_of_type :: proc(count: int, $TT: typeid) -> (res: []TT, backing: ^Buffer, err: bool) {
+	backing = new(Buffer);
+	size := size_of(TT) * count;
+	buffer_init_allocator(backing, size, size, context.allocator);
+	res   = mem.slice_data_cast([]TT, backing.buf[:]);
+	if len(res) != count {
+		err = true;
+	}
+	return;
+}