1404 lines
32 KiB
C
1404 lines
32 KiB
C
////////////////////////////////
|
|
// NOTE(allen): Static Configuration
|
|
|
|
#if !defined(MEM_DEFAULT_RESERVE_SIZE)
|
|
# define MEM_DEFAULT_RESERVE_SIZE GB(1)
|
|
#endif
|
|
#if !defined(MEM_COMMIT_BLOCK_SIZE)
|
|
# define MEM_COMMIT_BLOCK_SIZE MB(64)
|
|
#endif
|
|
#if !defined(MEM_MAX_ALIGN)
|
|
# define MEM_MAX_ALIGN 64
|
|
#endif
|
|
#if !defined(MEM_SCRATCH_POOL_COUNT)
|
|
# define MEM_SCRATCH_POOL_COUNT 2
|
|
#endif
|
|
|
|
////////////////////////////////
|
|
// NOTE(allen): Symbolic Constant Functions
|
|
|
|
link_function OperatingSystem
|
|
operating_system_from_context(void){
|
|
OperatingSystem result = OperatingSystem_Null;
|
|
#if OS_WINDOWS
|
|
result = OperatingSystem_Windows;
|
|
#elif OS_LINUX
|
|
result = OperatingSystem_Linux;
|
|
#elif OS_MAC
|
|
result = OperatingSystem_Mac;
|
|
#endif
|
|
return(result);
|
|
}
|
|
|
|
link_function Architecture
|
|
architecture_from_context(void){
|
|
Architecture result = Architecture_Null;
|
|
#if ARCH_X64
|
|
result = Architecture_X64;
|
|
#elif ARCH_X86
|
|
result = Architecture_X86;
|
|
#elif ARCH_ARM
|
|
result = Architecture_Arm;
|
|
#elif ARCH_ARM64
|
|
result = Architecture_Arm64;
|
|
#endif
|
|
return(result);
|
|
}
|
|
|
|
link_function char*
|
|
string_from_operating_system(OperatingSystem os){
|
|
char *result = "(null)";
|
|
switch (os){
|
|
case OperatingSystem_Windows:
|
|
{
|
|
result = "windows";
|
|
}break;
|
|
case OperatingSystem_Linux:
|
|
{
|
|
result = "linux";
|
|
}break;
|
|
case OperatingSystem_Mac:
|
|
{
|
|
result = "mac";
|
|
}break;
|
|
}
|
|
return(result);
|
|
}
|
|
|
|
link_function char*
|
|
string_from_architecture(Architecture arch){
|
|
char *result = "(null)";
|
|
switch (arch){
|
|
case Architecture_X64:
|
|
{
|
|
result = "x64";
|
|
}break;
|
|
case Architecture_X86:
|
|
{
|
|
result = "x86";
|
|
}break;
|
|
case Architecture_Arm:
|
|
{
|
|
result = "arm";
|
|
}break;
|
|
case Architecture_Arm64:
|
|
{
|
|
result = "armm64";
|
|
}break;
|
|
}
|
|
return(result);
|
|
}
|
|
|
|
link_function char*
|
|
string_from_month(Month month){
|
|
char *result = "(null)";
|
|
switch (month){
|
|
case Month_Jan:
|
|
{
|
|
result = "jan";
|
|
}break;
|
|
case Month_Feb:
|
|
{
|
|
result = "feb";
|
|
}break;
|
|
case Month_Mar:
|
|
{
|
|
result = "mar";
|
|
}break;
|
|
case Month_Apr:
|
|
{
|
|
result = "apr";
|
|
}break;
|
|
case Month_May:
|
|
{
|
|
result = "may";
|
|
}break;
|
|
case Month_Jun:
|
|
{
|
|
result = "jun";
|
|
}break;
|
|
case Month_Jul:
|
|
{
|
|
result = "jul";
|
|
}break;
|
|
case Month_Aug:
|
|
{
|
|
result = "aug";
|
|
}break;
|
|
case Month_Sep:
|
|
{
|
|
result = "sep";
|
|
}break;
|
|
case Month_Oct:
|
|
{
|
|
result = "oct";
|
|
}break;
|
|
case Month_Nov:
|
|
{
|
|
result = "nov";
|
|
}break;
|
|
case Month_Dec:
|
|
{
|
|
result = "dec";
|
|
}break;
|
|
}
|
|
return(result);
|
|
}
|
|
|
|
link_function char*
|
|
string_from_day_of_week(DayOfWeek day_of_week){
|
|
char *result = "(null)";
|
|
switch (day_of_week){
|
|
case DayOfWeek_Sunday:
|
|
{
|
|
result = "sunday";
|
|
}break;
|
|
case DayOfWeek_Monday:
|
|
{
|
|
result = "monday";
|
|
}break;
|
|
case DayOfWeek_Tuesday:
|
|
{
|
|
result = "tuesday";
|
|
}break;
|
|
case DayOfWeek_Wednesday:
|
|
{
|
|
result = "wednesday";
|
|
}break;
|
|
case DayOfWeek_Thursday:
|
|
{
|
|
result = "thursday";
|
|
}break;
|
|
case DayOfWeek_Friday:
|
|
{
|
|
result = "friday";
|
|
}break;
|
|
case DayOfWeek_Saturday:
|
|
{
|
|
result = "saturday";
|
|
}break;
|
|
}
|
|
return(result);
|
|
}
|
|
|
|
////////////////////////////////
|
|
// NOTE(allen): Time Functions
|
|
|
|
link_function DenseTime
|
|
dense_time_from_date_time(DateTime *in){
|
|
U32 year_encoded = (U32)((S32)in->year + 0x8000);
|
|
DenseTime result = 0;
|
|
result += year_encoded;
|
|
result *= 12;
|
|
result += (in->mon - 1);
|
|
result *= 31;
|
|
result += (in->day - 1);
|
|
result *= 24;
|
|
result += in->hour;
|
|
result *= 60;
|
|
result += in->min;
|
|
result *= 61;
|
|
result += in->sec;
|
|
result *= 1000;
|
|
result += in->msec;
|
|
return(result);
|
|
}
|
|
|
|
link_function DateTime
|
|
date_time_from_dense_time(DenseTime in){
|
|
DateTime result = {0};
|
|
result.msec = in%1000;
|
|
in /= 1000;
|
|
result.sec = in%61;
|
|
in /= 61;
|
|
result.min = in%60;
|
|
in /= 60;
|
|
result.hour = in%24;
|
|
in /= 24;
|
|
result.day = (in%31) + 1;
|
|
in /= 31;
|
|
result.mon = (in%12) + 1;
|
|
in /= 12;
|
|
S32 year_encoded = (S32)in;
|
|
result.year = (year_encoded - 0x8000);
|
|
return(result);
|
|
}
|
|
|
|
////////////////////////////////
|
|
// NOTE(allen): Memory Functions
|
|
|
|
#define MEM_INITIAL_COMMIT KB(4)
|
|
#define MEM_INTERNAL_MIN_SIZE AlignUpPow2(sizeof(Arena), MEM_MAX_ALIGN)
|
|
|
|
StaticAssert(sizeof(Arena) <= MEM_INITIAL_COMMIT,
|
|
mem_check_arena_size);
|
|
|
|
StaticAssert(IsPow2OrZero(MEM_COMMIT_BLOCK_SIZE) &&
|
|
MEM_COMMIT_BLOCK_SIZE != 0,
|
|
mem_check_commit_block_size);
|
|
|
|
StaticAssert(IsPow2OrZero(MEM_MAX_ALIGN) && MEM_MAX_ALIGN != 0,
|
|
mem_check_max_align);
|
|
|
|
link_function Arena*
|
|
arena_alloc_reserve(U64 reserve_size, B32 growing){
|
|
ProfBeginFunc();
|
|
|
|
Arena *result = 0;
|
|
if (reserve_size >= MEM_INITIAL_COMMIT){
|
|
void *memory = mem_reserve(reserve_size);
|
|
if (mem_commit(memory, MEM_INITIAL_COMMIT)){
|
|
AsanPoison(memory, reserve_size);
|
|
AsanUnpoison(memory, MEM_INTERNAL_MIN_SIZE);
|
|
result = (Arena*)memory;
|
|
result->current = result;
|
|
result->prev = 0;
|
|
result->alignment = sizeof(void*);
|
|
result->growing = growing;
|
|
result->base_pos = 0;
|
|
result->chunk_cap = reserve_size;
|
|
result->chunk_pos = MEM_INTERNAL_MIN_SIZE;
|
|
result->chunk_commit_pos = MEM_INITIAL_COMMIT;
|
|
}
|
|
}
|
|
Assert(result != 0);
|
|
|
|
ProfEndFunc();
|
|
return(result);
|
|
}
|
|
|
|
link_function Arena*
|
|
arena_alloc(void){
|
|
Arena *result = arena_alloc_reserve(MEM_DEFAULT_RESERVE_SIZE, 1);
|
|
return(result);
|
|
}
|
|
|
|
link_function void
|
|
arena_release(Arena *arena){
|
|
ProfBeginFunc();
|
|
|
|
Arena *ptr = arena->current;
|
|
for (;ptr != 0;){
|
|
Arena *prev = ptr->prev;
|
|
AsanPoison(ptr, ptr->chunk_cap);
|
|
mem_release(ptr, ptr->chunk_cap);
|
|
ptr = prev;
|
|
}
|
|
|
|
ProfEndFunc();
|
|
}
|
|
|
|
link_function void*
|
|
arena_push_no_zero(Arena *arena, U64 size){
|
|
ProfBeginFunc();
|
|
|
|
void *result = 0;
|
|
|
|
Arena *current = arena->current;
|
|
|
|
// allocate new chunk if necessary
|
|
if (arena->growing){
|
|
U64 next_chunk_pos = AlignUpPow2(current->chunk_pos, arena->alignment);
|
|
next_chunk_pos += size;
|
|
if (next_chunk_pos > current->chunk_cap){
|
|
U64 new_reserve_size = MEM_DEFAULT_RESERVE_SIZE;
|
|
U64 enough_to_fit = size + MEM_INTERNAL_MIN_SIZE;
|
|
if (new_reserve_size < enough_to_fit){
|
|
new_reserve_size = AlignUpPow2(enough_to_fit, KB(4));
|
|
}
|
|
|
|
void *memory = mem_reserve(new_reserve_size);
|
|
if (mem_commit(memory, MEM_INITIAL_COMMIT)){
|
|
AsanPoison(memory, new_reserve_size);
|
|
AsanUnpoison(memory, MEM_INTERNAL_MIN_SIZE);
|
|
Arena *new_chunk = (Arena*)memory;
|
|
new_chunk->prev = current;
|
|
new_chunk->base_pos = current->base_pos + current->chunk_cap;
|
|
new_chunk->chunk_cap = new_reserve_size;
|
|
new_chunk->chunk_pos = MEM_INTERNAL_MIN_SIZE;
|
|
new_chunk->chunk_commit_pos = MEM_INITIAL_COMMIT;
|
|
current = arena->current = new_chunk;
|
|
}
|
|
}
|
|
}
|
|
|
|
{
|
|
// if there is room in this chunk's reserve ...
|
|
U64 result_pos = AlignUpPow2(current->chunk_pos, arena->alignment);
|
|
U64 next_chunk_pos = result_pos + size;
|
|
if (next_chunk_pos <= current->chunk_cap){
|
|
|
|
// commit more memory if necessary
|
|
if (next_chunk_pos > current->chunk_commit_pos){
|
|
U64 next_commit_pos_aligned =
|
|
AlignUpPow2(next_chunk_pos, MEM_COMMIT_BLOCK_SIZE);
|
|
U64 next_commit_pos =
|
|
ClampTop(next_commit_pos_aligned, current->chunk_cap);
|
|
U64 commit_size = next_commit_pos - current->chunk_commit_pos;
|
|
if (mem_commit((U8*)current + current->chunk_commit_pos, commit_size)){
|
|
arena->chunk_commit_pos = next_commit_pos;
|
|
}
|
|
}
|
|
|
|
// if there is room in the commit range, return memory & advance pos
|
|
if (next_chunk_pos <= current->chunk_commit_pos){
|
|
AsanUnpoison((U8*)current + current->chunk_pos,
|
|
next_chunk_pos - current->chunk_pos);
|
|
result = (U8*)current + result_pos;
|
|
current->chunk_pos = next_chunk_pos;
|
|
}
|
|
}
|
|
}
|
|
|
|
ProfEndFunc();
|
|
return(result);
|
|
}
|
|
|
|
link_function void
|
|
arena_pop_to(Arena *arena, U64 pos){
|
|
ProfBeginFunc();
|
|
|
|
Arena *current = arena->current;
|
|
U64 total_pos = current->base_pos + current->chunk_pos;
|
|
if (pos < total_pos){
|
|
// release all chunks that begin after this pos
|
|
U64 clamped_total_pos = ClampBot(pos, MEM_INTERNAL_MIN_SIZE);
|
|
for (; clamped_total_pos < current->base_pos; ){
|
|
Arena *prev = current->prev;
|
|
AsanPoison(current, current->chunk_cap);
|
|
mem_release(current, current->chunk_cap);
|
|
current = prev;
|
|
}
|
|
|
|
// update arena's current
|
|
{
|
|
arena->current = current;
|
|
}
|
|
|
|
// update the chunk position of the chunk in
|
|
// the chain that contains this position.
|
|
{
|
|
U64 chunk_pos = clamped_total_pos - current->base_pos;
|
|
U64 clamped_chunk_pos = ClampBot(chunk_pos, MEM_INTERNAL_MIN_SIZE);
|
|
AsanPoison((U8*)current + clamped_chunk_pos,
|
|
current->chunk_pos - clamped_chunk_pos);
|
|
current->chunk_pos = clamped_chunk_pos;
|
|
}
|
|
}
|
|
|
|
ProfEndFunc();
|
|
}
|
|
|
|
link_function U64
|
|
arena_current_pos(Arena *arena){
|
|
Arena *current = arena->current;
|
|
U64 result = current->base_pos + current->chunk_pos;
|
|
return(result);
|
|
}
|
|
|
|
link_function void*
|
|
arena_push(Arena *arena, U64 size){
|
|
void *result = arena_push_no_zero(arena, size);
|
|
MemoryZero(result, size);
|
|
return(result);
|
|
}
|
|
|
|
link_function void
|
|
arena_align(Arena *arena, U64 pow2_align){
|
|
Assert(IsPow2OrZero(pow2_align) && pow2_align != 0 &&
|
|
pow2_align <= MEM_MAX_ALIGN);
|
|
Arena *current = arena->current;
|
|
U64 p = current->chunk_pos;
|
|
U64 p_aligned = AlignUpPow2(p, pow2_align);
|
|
U64 z = p_aligned - p;
|
|
if (z > 0){
|
|
arena_push(arena, z);
|
|
}
|
|
}
|
|
|
|
link_function void
|
|
arena_pop_amount(Arena *arena, U64 amount){
|
|
Arena *current = arena->current;
|
|
U64 total_pos = current->base_pos + current->chunk_pos;
|
|
if (amount <= total_pos){
|
|
U64 new_pos = total_pos - amount;
|
|
arena_pop_to(arena, new_pos);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////
|
|
// NOTE(allen): Temp Helper Functions
|
|
|
|
link_function ArenaTemp
|
|
arena_begin_temp(Arena *arena){
|
|
U64 pos = arena_current_pos(arena);
|
|
ArenaTemp temp = {arena, pos};
|
|
return(temp);
|
|
}
|
|
|
|
link_function void
|
|
arena_end_temp(ArenaTemp *temp){
|
|
arena_pop_to(temp->arena, temp->pos);
|
|
}
|
|
|
|
////////////////////////////////
|
|
// NOTE(allen): Scratch
|
|
|
|
threadvar Arena *m__scratch_pool[MEM_SCRATCH_POOL_COUNT] = {0};
|
|
|
|
link_function ArenaTemp
|
|
arena_get_scratch(Arena **conflict_array, U32 count){
|
|
ProfBeginFunc();
|
|
|
|
// init on first time
|
|
if (m__scratch_pool[0] == 0){
|
|
Arena **scratch_slot = m__scratch_pool;
|
|
for (U64 i = 0;
|
|
i < MEM_SCRATCH_POOL_COUNT;
|
|
i += 1, scratch_slot += 1){
|
|
*scratch_slot = arena_alloc();
|
|
}
|
|
}
|
|
|
|
// get non-conflicting arena
|
|
ArenaTemp result = {0};
|
|
Arena **scratch_slot = m__scratch_pool;
|
|
for (U64 i = 0;
|
|
i < MEM_SCRATCH_POOL_COUNT;
|
|
i += 1, scratch_slot += 1){
|
|
B32 is_non_conflict = 1;
|
|
Arena **conflict_ptr = conflict_array;
|
|
for (U32 j = 0; j < count; j += 1, conflict_ptr += 1){
|
|
if (*scratch_slot == *conflict_ptr){
|
|
is_non_conflict = 0;
|
|
break;
|
|
}
|
|
}
|
|
if (is_non_conflict){
|
|
result = arena_begin_temp(*scratch_slot);
|
|
break;
|
|
}
|
|
}
|
|
|
|
ProfEndFunc();
|
|
return(result);
|
|
}
|
|
|
|
////////////////////////////////
|
|
// NOTE(allen): String Compound Constructor Functions
|
|
|
|
link_function void
|
|
str8_list_push_explicit(String8List *list, String8 string,
|
|
String8Node *node_memory){
|
|
node_memory->string = string;
|
|
SLLQueuePush(list->first, list->last, node_memory);
|
|
list->node_count += 1;
|
|
list->total_size += string.size;
|
|
}
|
|
|
|
link_function void
|
|
str8_list_push(Arena *arena, String8List *list, String8 string){
|
|
String8Node *node = push_array(arena, String8Node, 1);
|
|
str8_list_push_explicit(list, string, node);
|
|
}
|
|
|
|
link_function String8
|
|
str8_join(Arena *arena, String8List *list,
|
|
StringJoin *join_optional){
|
|
ProfBeginFunc();
|
|
|
|
// setup join parameters
|
|
local StringJoin dummy_join = {0};
|
|
StringJoin *join = join_optional;
|
|
if (join == 0){
|
|
join = &dummy_join;
|
|
}
|
|
|
|
// compute total size
|
|
U64 size = (join->pre.size +
|
|
join->post.size +
|
|
join->mid.size*(list->node_count - 1) +
|
|
list->total_size);
|
|
|
|
// begin string build
|
|
U8 *str = push_array(arena, U8, size + 1);
|
|
U8 *ptr = str;
|
|
|
|
// write pre
|
|
MemoryCopy(ptr, join->pre.str, join->pre.size);
|
|
ptr += join->pre.size;
|
|
|
|
B32 is_mid = 0;
|
|
for (String8Node *node = list->first;
|
|
node != 0;
|
|
node = node->next){
|
|
// write mid
|
|
if (is_mid){
|
|
MemoryCopy(ptr, join->mid.str, join->mid.size);
|
|
ptr += join->mid.size;
|
|
}
|
|
|
|
// write node string
|
|
MemoryCopy(ptr, node->string.str, node->string.size);
|
|
ptr += node->string.size;
|
|
|
|
is_mid = 1;
|
|
}
|
|
|
|
// write post
|
|
MemoryCopy(ptr, join->post.str, join->post.size);
|
|
ptr += join->post.size;
|
|
|
|
// write null
|
|
*ptr = 0;
|
|
|
|
String8 result = str8(str, size);
|
|
ProfEndFunc();
|
|
return(result);
|
|
}
|
|
|
|
link_function String8List
|
|
str8_split(Arena *arena, String8 string, U8 *splits, U32 count){
|
|
ProfBeginFunc();
|
|
|
|
String8List result = {0};
|
|
|
|
U8 *ptr = string.str;
|
|
U8 *word_first = ptr;
|
|
U8 *opl = string.str + string.size;
|
|
for (;ptr < opl; ptr += 1){
|
|
// is this a split
|
|
U8 byte = *ptr;
|
|
B32 is_split_byte = 0;
|
|
for (U32 i = 0; i < count; i += 1){
|
|
if (byte == splits[i]){
|
|
is_split_byte = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (is_split_byte){
|
|
// try to emit word, advance word first pointer
|
|
if (word_first < ptr){
|
|
str8_list_push(arena, &result, str8_range(word_first, ptr));
|
|
}
|
|
word_first = ptr + 1;
|
|
}
|
|
}
|
|
|
|
// try to emit final word
|
|
if (word_first < ptr){
|
|
str8_list_push(arena, &result, str8_range(word_first, ptr));
|
|
}
|
|
|
|
ProfEndFunc();
|
|
return(result);
|
|
}
|
|
|
|
// TODO(allen): where do I want this to live??
|
|
#include <stdio.h>
|
|
|
|
link_function String8
|
|
str8_pushfv(Arena *arena, char *fmt, va_list args){
|
|
ProfBeginFunc();
|
|
|
|
// in case we need to try a second time
|
|
va_list args2;
|
|
va_copy(args2, args);
|
|
|
|
// try to build the string in 1024 bytes
|
|
U64 buffer_size = 1024;
|
|
U8 *buffer = push_array(arena, U8, buffer_size);
|
|
U64 actual_size = vsnprintf((char*)buffer, buffer_size, fmt, args);
|
|
|
|
String8 result = {0};
|
|
if (actual_size < buffer_size){
|
|
// if first try worked, put back what we didn't use and finish
|
|
arena_pop_amount(arena, buffer_size - actual_size - 1);
|
|
result = str8(buffer, actual_size);
|
|
}
|
|
else{
|
|
// if first try failed, reset and try again with correct size
|
|
arena_pop_amount(arena, buffer_size);
|
|
U8 *fixed_buffer = push_array(arena, U8, actual_size + 1);
|
|
U64 final_size = vsnprintf((char*)fixed_buffer, actual_size + 1, fmt, args2);
|
|
result = str8(fixed_buffer, final_size);
|
|
}
|
|
|
|
// end args2
|
|
va_end(args2);
|
|
|
|
ProfEndFunc();
|
|
return(result);
|
|
}
|
|
|
|
link_function String8
|
|
str8_pushf(Arena *arena, char *fmt, ...){
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
String8 result = str8_pushfv(arena, fmt, args);
|
|
va_end(args);
|
|
return(result);
|
|
}
|
|
|
|
link_function void
|
|
str8_list_pushf(Arena *arena, String8List *list, char *fmt, ...){
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
String8 string = str8_pushfv(arena, fmt, args);
|
|
va_end(args);
|
|
str8_list_push(arena, list, string);
|
|
}
|
|
|
|
link_function String8
|
|
str8_push_copy(Arena *arena, String8 string){
|
|
String8 result = {0};
|
|
result.str = push_array(arena, U8, string.size + 1);
|
|
result.size = string.size;
|
|
MemoryCopy(result.str, string.str, string.size);
|
|
result.str[result.size] = 0;
|
|
return(result);
|
|
}
|
|
|
|
////////////////////////////////
|
|
// NOTE(allen): String Comparison Functions
|
|
|
|
link_function B32
|
|
str8_match(String8 a, String8 b, StringMatchFlags flags){
|
|
ProfBeginFunc();
|
|
|
|
B32 result = 0;
|
|
if (a.size == b.size){
|
|
result = 1;
|
|
B32 no_case = ((flags & StringMatchFlag_NoCase) != 0);
|
|
for (U64 i = 0; i < a.size; i += 1){
|
|
U8 ac = a.str[i];
|
|
U8 bc = b.str[i];
|
|
if (no_case){
|
|
ac = str8_char_uppercase(ac);
|
|
bc = str8_char_uppercase(bc);
|
|
}
|
|
if (ac != bc){
|
|
result = 0;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
ProfEndFunc();
|
|
return(result);
|
|
}
|
|
|
|
////////////////////////////////
|
|
// NOTE(allen): Unicode Functions
|
|
|
|
link_function StringDecode
|
|
str_decode_utf8(U8 *str, U32 cap){
|
|
ProfBeginFunc();
|
|
|
|
local U8 length[] = {
|
|
1, 1, 1, 1, // 000xx
|
|
1, 1, 1, 1,
|
|
1, 1, 1, 1,
|
|
1, 1, 1, 1,
|
|
0, 0, 0, 0, // 100xx
|
|
0, 0, 0, 0,
|
|
2, 2, 2, 2, // 110xx
|
|
3, 3, // 1110x
|
|
4, // 11110
|
|
0, // 11111
|
|
};
|
|
local U8 first_byte_mask[] = { 0, 0x7F, 0x1F, 0x0F, 0x07 };
|
|
local U8 final_shift[] = { 0, 18, 12, 6, 0 };
|
|
|
|
StringDecode result = {0};
|
|
if (cap > 0){
|
|
result.codepoint = '#';
|
|
result.size = 1;
|
|
|
|
U8 byte = str[0];
|
|
U8 l = length[byte >> 3];
|
|
if (0 < l && l <= cap){
|
|
U32 cp = (byte & first_byte_mask[l]) << 18;
|
|
switch (l){
|
|
case 4: cp |= ((str[3] & 0x3F) << 0);
|
|
case 3: cp |= ((str[2] & 0x3F) << 6);
|
|
case 2: cp |= ((str[1] & 0x3F) << 12);
|
|
default: break;
|
|
}
|
|
cp >>= final_shift[l];
|
|
|
|
result.codepoint = cp;
|
|
result.size = l;
|
|
}
|
|
}
|
|
|
|
ProfEndFunc();
|
|
return(result);
|
|
}
|
|
|
|
link_function U32
|
|
str_encode_utf8(U8 *dst, U32 codepoint){
|
|
ProfBeginFunc();
|
|
|
|
U32 size = 0;
|
|
if (codepoint < (1 << 8)){
|
|
dst[0] = codepoint;
|
|
size = 1;
|
|
}
|
|
else if (codepoint < (1 << 11)){
|
|
dst[0] = 0xC0 | (codepoint >> 6);
|
|
dst[1] = 0x80 | (codepoint & 0x3F);
|
|
size = 2;
|
|
}
|
|
else if (codepoint < (1 << 16)){
|
|
dst[0] = 0xE0 | (codepoint >> 12);
|
|
dst[1] = 0x80 | ((codepoint >> 6) & 0x3F);
|
|
dst[2] = 0x80 | (codepoint & 0x3F);
|
|
size = 3;
|
|
}
|
|
else if (codepoint < (1 << 21)){
|
|
dst[0] = 0xF0 | (codepoint >> 18);
|
|
dst[1] = 0x80 | ((codepoint >> 12) & 0x3F);
|
|
dst[2] = 0x80 | ((codepoint >> 6) & 0x3F);
|
|
dst[3] = 0x80 | (codepoint & 0x3F);
|
|
size = 4;
|
|
}
|
|
else{
|
|
dst[0] = '#';
|
|
size = 1;
|
|
}
|
|
|
|
ProfEndFunc();
|
|
return(size);
|
|
}
|
|
|
|
link_function StringDecode
|
|
str_decode_utf16(U16 *str, U32 cap){
|
|
ProfBeginFunc();
|
|
|
|
StringDecode result = {'#', 1};
|
|
U16 x = str[0];
|
|
if (x < 0xD800 || 0xDFFF < x){
|
|
result.codepoint = x;
|
|
}
|
|
else if (cap >= 2){
|
|
U16 y = str[1];
|
|
if (0xD800 <= x && x < 0xDC00 &&
|
|
0xDC00 <= y && y < 0xE000){
|
|
U16 xj = x - 0xD800;
|
|
U16 yj = y - 0xDc00;
|
|
U32 xy = (xj << 10) | yj;
|
|
result.codepoint = xy + 0x10000;
|
|
result.size = 2;
|
|
}
|
|
}
|
|
|
|
ProfEndFunc();
|
|
return(result);
|
|
}
|
|
|
|
link_function U32
|
|
str_encode_utf16(U16 *dst, U32 codepoint){
|
|
ProfBeginFunc();
|
|
|
|
U32 size = 0;
|
|
if (codepoint < 0x10000){
|
|
dst[0] = codepoint;
|
|
size = 1;
|
|
}
|
|
else{
|
|
U32 cpj = codepoint - 0x10000;
|
|
dst[0] = (cpj >> 10) + 0xD800;
|
|
dst[1] = (cpj & 0x3FF) + 0xDC00;
|
|
size = 2;
|
|
}
|
|
|
|
ProfEndFunc();
|
|
return(size);
|
|
}
|
|
|
|
link_function String32
|
|
str32_from_str8(Arena *arena, String8 string){
|
|
ProfBeginFunc();
|
|
|
|
U32 *memory = push_array(arena, U32, string.size + 1);
|
|
|
|
U32 *dptr = memory;
|
|
U8 *ptr = string.str;
|
|
U8 *opl = string.str + string.size;
|
|
for (; ptr < opl;){
|
|
StringDecode decode = str_decode_utf8(ptr, (U64)(opl - ptr));
|
|
*dptr = decode.codepoint;
|
|
ptr += decode.size;
|
|
dptr += 1;
|
|
}
|
|
|
|
*dptr = 0;
|
|
|
|
U64 alloc_count = string.size + 1;
|
|
U64 string_count = (U64)(dptr - memory);
|
|
U64 unused_count = alloc_count - string_count - 1;
|
|
arena_pop_amount(arena, unused_count*sizeof(*memory));
|
|
|
|
String32 result = {memory, string_count};
|
|
ProfEndFunc();
|
|
return(result);
|
|
}
|
|
|
|
link_function String8
|
|
str8_from_str32(Arena *arena, String32 string){
|
|
ProfBeginFunc();
|
|
|
|
U8 *memory = push_array(arena, U8, string.size*4 + 1);
|
|
|
|
U8 *dptr = memory;
|
|
U32 *ptr = string.str;
|
|
U32 *opl = string.str + string.size;
|
|
for (; ptr < opl;){
|
|
U32 size = str_encode_utf8(dptr, *ptr);
|
|
ptr += 1;
|
|
dptr += size;
|
|
}
|
|
|
|
*dptr = 0;
|
|
|
|
U64 alloc_count = string.size*4 + 1;
|
|
U64 string_count = (U64)(dptr - memory);
|
|
U64 unused_count = alloc_count - string_count - 1;
|
|
arena_pop_amount(arena, unused_count*sizeof(*memory));
|
|
|
|
String8 result = {memory, string_count};
|
|
ProfEndFunc();
|
|
return(result);
|
|
}
|
|
|
|
link_function String16
|
|
str16_from_str8(Arena *arena, String8 string){
|
|
ProfBeginFunc();
|
|
U16 *memory = push_array(arena, U16, string.size*2 + 1);
|
|
|
|
U16 *dptr = memory;
|
|
U8 *ptr = string.str;
|
|
U8 *opl = string.str + string.size;
|
|
for (; ptr < opl;){
|
|
StringDecode decode = str_decode_utf8(ptr, (U64)(opl - ptr));
|
|
U32 enc_size = str_encode_utf16(dptr, decode.codepoint);
|
|
ptr += decode.size;
|
|
dptr += enc_size;
|
|
}
|
|
|
|
*dptr = 0;
|
|
|
|
U64 alloc_count = string.size*2 + 1;
|
|
U64 string_count = (U64)(dptr - memory);
|
|
U64 unused_count = alloc_count - string_count - 1;
|
|
arena_pop_amount(arena, unused_count*sizeof(*memory));
|
|
|
|
String16 result = {memory, string_count};
|
|
ProfEndFunc();
|
|
return(result);
|
|
}
|
|
|
|
link_function String8
|
|
str8_from_str16(Arena *arena, String16 string){
|
|
ProfBeginFunc();
|
|
U8 *memory = push_array(arena, U8, string.size*3 + 1);
|
|
|
|
U8 *dptr = memory;
|
|
U16 *ptr = string.str;
|
|
U16 *opl = string.str + string.size;
|
|
for (; ptr < opl;){
|
|
StringDecode decode = str_decode_utf16(ptr, (U64)(opl - ptr));
|
|
U16 enc_size = str_encode_utf8(dptr, decode.codepoint);
|
|
ptr += decode.size;
|
|
dptr += enc_size;
|
|
}
|
|
|
|
*dptr = 0;
|
|
|
|
U64 alloc_count = string.size*3 + 1;
|
|
U64 string_count = (U64)(dptr - memory);
|
|
U64 unused_count = alloc_count - string_count - 1;
|
|
arena_pop_amount(arena, unused_count*sizeof(*memory));
|
|
|
|
String8 result = {memory, string_count};
|
|
ProfEndFunc();
|
|
return(result);
|
|
}
|
|
|
|
////////////////////////////////
|
|
// NOTE(allen): Log
|
|
|
|
threadvar LOG_ThreadVars *log_vars = 0;
|
|
|
|
link_function void
|
|
log_accum_begin(void){
|
|
LOG_ThreadVars *vars = log_vars;
|
|
if (vars == 0){
|
|
Arena *arena = arena_alloc();
|
|
vars = log_vars = push_array(arena, LOG_ThreadVars, 1);
|
|
vars->arena = arena;
|
|
}
|
|
|
|
U64 pos = arena_current_pos(vars->arena);
|
|
LOG_Node *node = push_array(vars->arena, LOG_Node, 1);
|
|
if (node == 0){
|
|
vars->over_stack += 1;
|
|
}
|
|
else{
|
|
node->pos = pos;
|
|
SLLStackPush(vars->stack, node);
|
|
}
|
|
}
|
|
|
|
link_function void
|
|
log_emit(String8 message){
|
|
LOG_ThreadVars *vars = log_vars;
|
|
if (vars != 0 &&
|
|
vars->over_stack == 0){
|
|
LOG_Node *node = vars->stack;
|
|
if (node != 0){
|
|
String8 msg_copy = str8_push_copy(vars->arena, message);
|
|
str8_list_push(vars->arena, &node->log, msg_copy);
|
|
}
|
|
}
|
|
}
|
|
|
|
link_function void
|
|
log_emitf(char *fmt, ...){
|
|
LOG_ThreadVars *vars = log_vars;
|
|
if (vars != 0 &&
|
|
vars->over_stack == 0){
|
|
LOG_Node *node = vars->stack;
|
|
if (node != 0){
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
String8 string = str8_pushfv(vars->arena, fmt, args);
|
|
va_end(args);
|
|
str8_list_push(vars->arena, &node->log, string);
|
|
}
|
|
}
|
|
}
|
|
|
|
link_function String8
|
|
log_accum_end(Arena *arena){
|
|
String8 result = {0};
|
|
LOG_ThreadVars *vars = log_vars;
|
|
if (vars != 0){
|
|
if (vars->over_stack == 0){
|
|
LOG_Node *node = vars->stack;
|
|
if (node != 0){
|
|
result = str8_join(arena, &node->log, 0);
|
|
SLLStackPop(vars->stack);
|
|
arena_pop_to(vars->arena, node->pos);
|
|
}
|
|
}
|
|
else{
|
|
vars->over_stack -= 1;
|
|
}
|
|
}
|
|
return(result);
|
|
}
|
|
|
|
////////////////////////////////
|
|
// NOTE(allen): Errors
|
|
|
|
// IMPORTANT: It is important that strings get pushed
|
|
// *on top* of the node that holds them within the arena.
|
|
// basically: Assert((U8*)node < (U8*)node->error.str);
|
|
// except that stops working if the arena is chained...
|
|
|
|
threadvar ER_ThreadVars *er_vars = 0;
|
|
|
|
link_function void
|
|
er_accum_begin(void){
|
|
ER_ThreadVars *vars = er_vars;
|
|
if (vars == 0){
|
|
Arena *arena = arena_alloc_reserve(KB(64), 0);
|
|
vars = er_vars = push_array(arena, ER_ThreadVars, 1);
|
|
vars->arena = arena;
|
|
}
|
|
|
|
U64 pos = arena_current_pos(vars->arena);
|
|
ER_Node *node = push_array(vars->arena, ER_Node, 1);
|
|
if (node == 0){
|
|
vars->over_stack += 1;
|
|
}
|
|
else{
|
|
node->pos = pos;
|
|
SLLStackPush(vars->stack, node);
|
|
}
|
|
}
|
|
|
|
link_function void
|
|
er_emit(String8 error){
|
|
ER_ThreadVars *vars = er_vars;
|
|
if (vars != 0 &&
|
|
vars->over_stack == 0){
|
|
ER_Node *node = vars->stack;
|
|
if (node != 0 && node->error.size == 0){
|
|
node->error = str8_push_copy(vars->arena, error);
|
|
}
|
|
}
|
|
}
|
|
|
|
link_function void
|
|
er_emitf(char *fmt, ...){
|
|
ER_ThreadVars *vars = er_vars;
|
|
if (vars != 0 &&
|
|
vars->over_stack == 0){
|
|
ER_Node *node = vars->stack;
|
|
if (node != 0 && node->error.size == 0){
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
String8 string = str8_pushfv(vars->arena, fmt, args);
|
|
va_end(args);
|
|
node->error = string;
|
|
}
|
|
}
|
|
}
|
|
|
|
link_function String8
|
|
er_accum_end(Arena *arena){
|
|
String8 result = {0};
|
|
ER_ThreadVars *vars = er_vars;
|
|
if (vars != 0){
|
|
if (vars->over_stack == 0){
|
|
ER_Node *node = vars->stack;
|
|
if (node != 0){
|
|
result = str8_push_copy(arena, node->error);
|
|
SLLStackPop(vars->stack);
|
|
arena_pop_to(vars->arena, node->pos);
|
|
}
|
|
}
|
|
else{
|
|
vars->over_stack -= 1;
|
|
}
|
|
}
|
|
return(result);
|
|
}
|
|
|
|
////////////////////////////////
|
|
// NOTE(allen): Interleaving Functions
|
|
|
|
link_function String8
|
|
bop_interleave(Arena *arena, void **in,
|
|
U64 lane_count, U64 el_size, U64 el_count){
|
|
// TODO(allen): look at disassembly for real speed work
|
|
|
|
// setup buffer
|
|
String8 result = {0};
|
|
result.size = lane_count*el_size*el_count;
|
|
result.str = push_array(arena, U8, result.size);
|
|
|
|
// fill loop
|
|
U8 *out_ptr = result.str;
|
|
U64 in_off = 0;
|
|
for (U64 i = 0; i < el_count; i += 1, in_off += el_size){
|
|
U8 **in_base_ptr = (U8**)in;
|
|
for (U64 j = 0; j < lane_count; j += 1, in_base_ptr += 1){
|
|
MemoryCopy(out_ptr, *in_base_ptr + in_off, el_size);
|
|
out_ptr += el_size;
|
|
}
|
|
}
|
|
|
|
return(result);
|
|
}
|
|
|
|
link_function String8*
|
|
bop_uninterleave(Arena *arena, void *in,
|
|
U64 lane_count, U64 el_size, U64 el_count){
|
|
// TODO(allen): look at disassembly for real speed work
|
|
|
|
// compute sizes
|
|
U64 bytes_per_lane = el_size*el_count;
|
|
U64 total_size = lane_count*bytes_per_lane;
|
|
|
|
// allocate outs
|
|
String8 *result = push_array(arena, String8, lane_count);
|
|
for (U64 i = 0; i < lane_count; i += 1){
|
|
result[i].str = push_array(arena, U8, bytes_per_lane);
|
|
result[i].size = bytes_per_lane;
|
|
}
|
|
|
|
// fill loop
|
|
U8 *in_ptr = (U8*)in;
|
|
U64 out_off = 0;
|
|
for (U64 i = 0; i < el_count; i += 1, out_off += el_size){
|
|
String8 *out_buffer = result;
|
|
for (U64 j = 0; j < lane_count; j += 1, out_buffer += 1){
|
|
MemoryCopy(out_buffer->str + out_off, in_ptr, el_size);
|
|
in_ptr += el_size;
|
|
}
|
|
}
|
|
|
|
return(result);
|
|
}
|
|
|
|
////////////////////////////////
|
|
// NOTE(allen): Conversions
|
|
|
|
link_function String8
|
|
bop_f32_from_s24(Arena *arena, String8 in){
|
|
// TODO(allen): look at disassembly for real speed work
|
|
|
|
// round size
|
|
U64 el_count = in.size/3;
|
|
|
|
// allocate out
|
|
F32 *out = push_array(arena, F32, el_count);
|
|
|
|
// fill loop
|
|
U8 *in_ptr = in.str;
|
|
F32 *out_ptr = out;
|
|
F32 *opl_ptr = out_ptr + el_count;
|
|
for (; out_ptr < opl_ptr; out_ptr += 1){
|
|
// read s24
|
|
S32 x = (*in_ptr);
|
|
in_ptr += 1;
|
|
x |= (*in_ptr) << 8;
|
|
in_ptr += 1;
|
|
x |= (*in_ptr) << 16;
|
|
in_ptr += 1;
|
|
if ((x & (1 << 23)) != 0){
|
|
x |= (0xFF << 24);
|
|
}
|
|
|
|
// divide to float
|
|
F32 fx = 0.f;
|
|
if (x >= 0){
|
|
fx = ((F32)x)/(8388607.f);
|
|
}
|
|
else{
|
|
fx = ((F32)x)/(8388608.f);
|
|
}
|
|
|
|
// write f32
|
|
*out_ptr = fx;
|
|
}
|
|
|
|
// package output buffer
|
|
String8 result = {0};
|
|
result.str = (U8*)out;
|
|
result.size = el_count*sizeof(F32);
|
|
return(result);
|
|
}
|
|
|
|
link_function String8
|
|
bop_s24_from_f32(Arena *arena, String8 in){
|
|
// TODO(allen): look at disassembly for real speed work
|
|
|
|
// round size
|
|
U64 el_count = in.size/4;
|
|
|
|
// allocate out
|
|
U8 *out = push_array(arena, U8, el_count*3);
|
|
|
|
// fill loop
|
|
F32 *in_ptr = (F32*)in.str;
|
|
U8 *out_ptr = out;
|
|
U8 *opl_ptr = out_ptr + el_count*3;
|
|
for (; out_ptr < opl_ptr; out_ptr += 3, in_ptr += 1){
|
|
// read f32
|
|
F32 x = (*in_ptr);
|
|
F32 x_clamped = Clamp(-1.f, x, 1.f);
|
|
|
|
// multiply to s24 (inside a s32)
|
|
S32 fx = 0;
|
|
if (x >= 0){
|
|
fx = 0x7FFFFF*x_clamped;
|
|
}
|
|
else{
|
|
fx = 0x800000*x_clamped;
|
|
}
|
|
|
|
// write s24
|
|
out_ptr[0] = fx&0xFF;
|
|
out_ptr[1] = (fx >> 8)&0xFF;
|
|
out_ptr[2] = (fx >> 16)&0xFF;
|
|
}
|
|
|
|
// package output buffer
|
|
String8 result = {0};
|
|
result.str = (U8*)out;
|
|
result.size = el_count*3;
|
|
return(result);
|
|
}
|
|
|
|
link_function String8
|
|
bop_s16_from_f32(Arena *arena, String8 in){
|
|
// TODO(allen): look at disassembly for real speed work
|
|
|
|
// round size
|
|
U64 el_count = in.size/4;
|
|
|
|
// allocate out
|
|
S16 *out = push_array(arena, S16, el_count);
|
|
|
|
// fill loop
|
|
F32 *in_ptr = (F32*)in.str;
|
|
S16 *out_ptr = out;
|
|
S16 *opl_ptr = out_ptr + el_count;
|
|
for (; out_ptr < opl_ptr; out_ptr += 1, in_ptr += 1){
|
|
// read f32
|
|
F32 x = (*in_ptr);
|
|
F32 x_clamped = Clamp(-1.f, x, 1.f);
|
|
|
|
// multiply to s16 (inside a s32)
|
|
S32 fx = 0;
|
|
if (x >= 0){
|
|
fx = 0x7FFF*x_clamped;
|
|
}
|
|
else{
|
|
fx = 0x8000*x_clamped;
|
|
}
|
|
|
|
// write s16
|
|
*out_ptr = fx;
|
|
}
|
|
|
|
// package output buffer
|
|
String8 result = {0};
|
|
result.str = (U8*)out;
|
|
result.size = el_count*sizeof(S16);
|
|
return(result);
|
|
}
|
|
|
|
|
|
////////////////////////////////
|
|
// NOTE(allen): PRNG Functions
|
|
|
|
#include "pcg/pcg_basic.c"
|
|
|
|
link_function PRNG
|
|
prng_make_from_seed(PRNG_Seed *seed){
|
|
PRNG prng = {0};
|
|
|
|
U64 st0 = *(U64*)(seed->v + 0);
|
|
U64 seq0 = *(U64*)(seed->v + 8);
|
|
U64 st1 = *(U64*)(seed->v + 16);
|
|
U64 seq1 = *(U64*)(seed->v + 24);
|
|
|
|
if ((seq0|1) == (seq1|1)){
|
|
seq1 = ~seq1;
|
|
}
|
|
|
|
pcg32_srandom_r(&prng.gen[0], st0, seq0);
|
|
pcg32_srandom_r(&prng.gen[1], st1, seq1);
|
|
|
|
return(prng);
|
|
}
|
|
|
|
link_function U32
|
|
prng_next_u32(PRNG *prng){
|
|
U32 result = pcg32_random_r(&prng->gen[0]);
|
|
return(result);
|
|
}
|
|
|
|
link_function U64
|
|
prng_next_u64(PRNG *prng){
|
|
U32 hi = pcg32_random_r(&prng->gen[0]);
|
|
U32 lo = pcg32_random_r(&prng->gen[1]);
|
|
U32 result = (((U64)hi) << 32) | lo;
|
|
return(result);
|
|
}
|
|
|
|
link_function U32
|
|
prng_next_bounded(PRNG *prng, U32 bound){
|
|
U32 threshold = (-bound)%bound;
|
|
U32 r = pcg32_random_r(&prng->gen[0]);
|
|
for (;r < threshold;){
|
|
r = pcg32_random_r(&prng->gen[0]);
|
|
}
|
|
U32 result = r%bound;
|
|
return(result);
|
|
}
|
|
|
|
// [0,1]
|
|
link_function F32
|
|
prng_next_unital_f32(PRNG *prng){
|
|
U32 x = prng_next_u32(prng);
|
|
U32 x_masked = (x & 0x1FFFFF); // 21-bit mask
|
|
F32 result = (F32)x_masked/2097151.f;
|
|
return(result);
|
|
}
|
|
|
|
// [-1,1]
|
|
link_function F32
|
|
prng_next_biunital_f32(PRNG *prng){
|
|
U32 x = prng_next_u32(prng);
|
|
U32 x_masked = (x & 0x3FFFFF); // 22-bit mask
|
|
F32 result = ((F32)x_masked/2097151.f) - 1.f;
|
|
return(result);
|
|
}
|
|
|
|
link_function B32
|
|
prng_next_pcoin(PRNG *prng, F32 p){
|
|
F32 x = prng_next_unital_f32(prng);
|
|
B32 result = (x <= p);
|
|
return(result);
|
|
}
|
|
|
|
////////////////////////////////
|
|
// NOTE(allen): Math Functions
|
|
|
|
link_function F32
|
|
math_gaussian(F32 sigma, F32 x){
|
|
// 1/(sqrt(2*pi)*sigma) * e^(-x*x/(2*sigma*sigma))
|
|
|
|
F32 sqrt_2pi = sqrt_F32(tau_F32);
|
|
F32 inv_sqrt_2pi_sigma = 1.f/(sqrt_2pi*sigma);
|
|
F32 sigma_p2 = sigma*sigma;
|
|
|
|
F32 exponent = -x*x/(2*sigma_p2);
|
|
F32 power = pow_F32(e_F32, exponent);
|
|
|
|
F32 result = inv_sqrt_2pi_sigma*power;
|
|
return(result);
|
|
}
|
|
|
|
link_function Array_F32
|
|
math_gaussian_kernel(Arena *arena, F32 sigma, U32 extra_reach){
|
|
Assert(sigma > 0.f);
|
|
|
|
// allocate
|
|
U32 reach = (U32)ceil_F32(sigma) + extra_reach;
|
|
U64 count = reach*2 + 1;
|
|
F32 *vals = push_array(arena, F32, count);
|
|
|
|
// calculate guassian samples & sum
|
|
F32 sum = 0;
|
|
{
|
|
F32 sqrt_2pi = sqrt_F32(tau_F32);
|
|
F32 inv_sqrt_2pi_sigma = 1.f/(sqrt_2pi*sigma);
|
|
F32 sigma_p2 = sigma*sigma;
|
|
|
|
F32 mid = (F32)reach;
|
|
F32 *val_ptr = vals;
|
|
for (U64 i = 0; i < count; i += 1, val_ptr += 1){
|
|
F32 x = (F32)i - mid;
|
|
F32 exponent = -x*x/(2*sigma_p2);
|
|
F32 power = pow_F32(e_F32, exponent);
|
|
F32 g = inv_sqrt_2pi_sigma*power;
|
|
|
|
sum += g;
|
|
*val_ptr = g;
|
|
}
|
|
}
|
|
|
|
// normalise kernel
|
|
{
|
|
F32 *val_ptr = vals;
|
|
for (U64 i = 0; i < count; i += 1, val_ptr += 1){
|
|
*val_ptr = *val_ptr/sum;
|
|
}
|
|
}
|
|
|
|
// return result
|
|
Array_F32 result = {0};
|
|
result.vals = vals;
|
|
result.count = count;
|
|
return(result);
|
|
}
|