Files
Odin/core/bytes/util.odin
2021-06-21 21:05:52 +02:00

187 lines
6.0 KiB
Odin

package bytes
/*
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
Made available under Odin's BSD-2 license.
List of contributors:
Jeroen van Rijn: Initial implementation.
`bytes.Buffer` type conversion helpers.
*/
import "core:intrinsics"
import "core:mem"
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;
}