Ver a proveniência

scheduler in ftb

master
Felix Brendel há 5 anos
ascendente
cometimento
501896cba1
8 ficheiros alterados com 812 adições e 15 eliminações
  1. +1
    -0
      bucket_allocator.hpp
  2. +7
    -7
      hooks.hpp
  3. +46
    -0
      macros.hpp
  4. +6
    -6
      print.hpp
  5. +607
    -0
      scheduler.cpp
  6. +51
    -0
      stacktrace.hpp
  7. +2
    -2
      tests/build.sh
  8. +92
    -0
      tests/main.cpp

+ 1
- 0
bucket_allocator.hpp Ver ficheiro

@@ -1,3 +1,4 @@
#pragma once
#include "arraylist.hpp"
#include "types.hpp"



+ 7
- 7
hooks.hpp Ver ficheiro

@@ -68,10 +68,10 @@ struct Hook {
}
};

struct __System_Shutdown_Hook : Hook {
void operator()() = delete;
~__System_Shutdown_Hook() {
Hook::operator()();
lambdas.dealloc();
}
} system_shutdown_hook;
// struct __System_Shutdown_Hook : Hook {
// void operator()() = delete;
// ~__System_Shutdown_Hook() {
// Hook::operator()();
// lambdas.dealloc();
// }
// } system_shutdown_hook;

+ 46
- 0
macros.hpp Ver ficheiro

@@ -1,4 +1,11 @@
#pragma once
#include "platform.hpp"

#ifdef FTB_WINDOWS
#else
# include <signal.h> // for sigtrap
#endif

#include <functional>

#define proc auto
@@ -131,3 +138,42 @@ else
call2(var);
}
*/

#define panic(...) \
do { \
print("%{color<}[Panic]%{>color} in " \
"file %{color<}%{->char}%{>color} " \
"line %{color<}%{u32}%{>color}: " \
"(%{color<}%{->char}%{>color})\n", \
console_red, console_cyan, __FILE__, console_cyan, __LINE__, \
console_cyan, __func__); \
print("%{color<}", console_red); \
print(__VA_ARGS__); \
print("%{>color}\n"); \
fflush(stdout); \
print_stacktrace(); \
debug_break(); \
} while(0)


#define panic_if(cond, ...) \
if(!(cond)); \
else panic(__VA_ARGS__)

#ifdef FTB_DEBUG_LOG
# define debug_log(...) \
do { \
print("%{color<}[INFO " __FILE__ ":%{color<}%{->char}" \
"%{>color}]%{>color} ", \
console_green, console_cyan, __func__); \
println(__VA_ARGS__); \
} while (0)
#else
# define debug_log(...)
#endif

#ifdef FTB_WINDOWS
# define debug_break() __debugbreak()
#else
# define debug_break() raise(SIGTRAP);
#endif

+ 6
- 6
print.hpp Ver ficheiro

@@ -500,12 +500,6 @@ void init_printer() {
printer_map.alloc();
type_map.alloc();

system_shutdown_hook << [](){
color_stack.dealloc();
printer_map.dealloc();
type_map.dealloc();
};

register_printer("spaces", print_spaces, Printer_Function_Type::_32b);
register_printer("u32", print_u32, Printer_Function_Type::_32b);
register_printer("u64", print_u64, Printer_Function_Type::_64b);
@@ -521,3 +515,9 @@ void init_printer() {
register_printer("->Str", print_Str, Printer_Function_Type::_ptr);
register_printer("->char_line", print_str_line, Printer_Function_Type::_ptr);
}

void deinit_printer() {
color_stack.dealloc();
printer_map.dealloc();
type_map.dealloc();
}

+ 607
- 0
scheduler.cpp Ver ficheiro

@@ -0,0 +1,607 @@
#pragma once
#include "macros.hpp"
#include "types.hpp"
#include "print.hpp"
#include "bucket_allocator.hpp"
#include "arraylist.hpp"
#include "stacktrace.hpp"

namespace Scheduler {

// NOTE(Felix): since we want to be able to interpolate any data in
// principle, and the animation needs the start and end values, but the
// size of the stuff we want to interpolate may vary, we just give it a
// small chunk of memory to be stored
typedef s64 Data_Block[22];


typedef void (*Closure_F)(Data_Block);
typedef void (*Lambda_F)();

typedef f32 (*Shaper_F)(f32 t);
typedef void (*Interpolator_F)(void* from, f32 t, void* to, void* interpolant);

enum struct Interpolation_Type : u8 {
Lerp = 1 << 7,
Ease_In,
Ease_Out,
Ease_Middle,
Ease_In_And_Out
};

enum struct Interpolant_Type : u8 {
F32 = 1 << 7,
};


struct Scheduled_Animation_Create_Info {
float seconds_to_start;
float seconds_to_end;

void* interpolant;
Interpolant_Type interpolant_type;

void* from;
void* to;

Interpolation_Type interpolation_type = Interpolation_Type::Lerp;
const char* name = "";
};


enum struct Function_Type {
Closure,
Lambda
};

struct Action_Create_Info {
Function_Type type;
union {
Lambda_F lambda;
Closure_F closure;
};
void* param = nullptr;
u32 param_size = 0;
const char* name = "";
};

struct Scheduled_Action_Create_Info {
float seconds_to_run;
Action_Create_Info action;
};

struct Action {
Function_Type type;
union {
Lambda_F lambda;
Closure_F closure;
};
Data_Block param;
Action* next;
u64 creation_stamp;
bool already_ran;
#ifdef FTB_INTERNAL_DEBUG
const char* name;
#endif
};

struct Scheduled_Action {
f32 seconds_to_run;
Action action;
};

struct Scheduled_Animation {
Interpolation_Type interpolation_type;
Interpolant_Type interpolant_type;

f32 seconds_to_start;
f32 seconds_to_end;

void* interpolant;

Data_Block from;
Data_Block to;

Action* next;
#ifdef FTB_INTERNAL_DEBUG
const char* name;
#endif
};

enum struct Schedulee_Type : u8 {
None,
Action,
Animation,
};

struct Action_Or_Animation {
Schedulee_Type type;
union {
Action* action;
Scheduled_Animation* animation;
};
};

// For user defined shapers t -> t
Shaper_F _shapers[127];
// Interpolators for user types
Interpolator_F _interpolators[127];
size_t _interpolatee_sizes[127];

// Per reset cycle:
// PING
Bucket_Allocator<Scheduled_Animation> active_animations_1;
Bucket_Allocator<Scheduled_Action> scheduled_actions_1;
Bucket_Allocator<Action> chained_actions_1;
// PONG
Bucket_Allocator<Scheduled_Animation> active_animations_2;
Bucket_Allocator<Scheduled_Action> scheduled_actions_2;
Bucket_Allocator<Action> chained_actions_2;

Bucket_Allocator<Scheduled_Animation>* active_animations;
Bucket_Allocator<Scheduled_Action>* scheduled_actions;
Bucket_Allocator<Action>* chained_actions;

Bucket_Allocator<Scheduled_Animation>* future_active_animations;
Bucket_Allocator<Scheduled_Action>* future_scheduled_actions;
Bucket_Allocator<Action>* future_chained_actions;

// Per Tick:
// if something is deleted while iterating over them
Array_List<Scheduled_Animation*> _animations_marked_for_deletion;
Array_List<Scheduled_Action*> _actions_marked_for_deletion;

Array_List<Action*> _chained_actions_to_be_run_after_iteration;

bool _is_iterating = false;
bool _should_reset = false;
bool _should_stop_iterating = false;
u64 _action_stamp_counter = 0;

auto init() -> void {
active_animations_1.alloc();
scheduled_actions_1.alloc();
chained_actions_1.alloc();

active_animations_2.alloc();
scheduled_actions_2.alloc();
chained_actions_2.alloc();

active_animations = &active_animations_1;
scheduled_actions = &scheduled_actions_1;
chained_actions = &chained_actions_1;

future_active_animations = &active_animations_2;
future_scheduled_actions = &scheduled_actions_2;
future_chained_actions = &chained_actions_2;


_animations_marked_for_deletion.alloc();
_actions_marked_for_deletion.alloc();
_chained_actions_to_be_run_after_iteration.alloc(20);
}

auto deinit() -> void {
active_animations_1.dealloc();
scheduled_actions_1.dealloc();
chained_actions_1.dealloc();

active_animations_2.dealloc();
scheduled_actions_2.dealloc();
chained_actions_2.dealloc();

_animations_marked_for_deletion.dealloc();
_actions_marked_for_deletion.dealloc();
_chained_actions_to_be_run_after_iteration.dealloc();
}

auto register_shaper(Shaper_F f, Interpolation_Type t) {
_shapers[(u8)t] = f;
}

auto register_interpolator(Interpolator_F f, Interpolant_Type t, size_t size_of_interpolant) {
_interpolatee_sizes[(u8)t] = size_of_interpolant;
_interpolators[(u8)t] = f;
}

auto schedule_animation(Scheduled_Animation_Create_Info aci) -> Scheduled_Animation* {
Scheduled_Animation* anim;
if (_should_reset) {
anim = future_active_animations->allocate();
} else {
anim = active_animations->allocate();
}

anim->next = nullptr;

#ifdef FTB_INTERNAL_DEBUG
anim->name = aci.name;
#endif

anim->interpolation_type = aci.interpolation_type;
anim->interpolant_type = aci.interpolant_type;

anim->seconds_to_start = aci.seconds_to_start;
anim->seconds_to_end = aci.seconds_to_end;

anim->interpolant = aci.interpolant;

u32 data_size;
switch(anim->interpolant_type) {
case Interpolant_Type::F32: data_size = sizeof(f32); break;
default: {
data_size = _interpolatee_sizes[(u8)(anim->interpolant_type)];
panic_if(data_size == 0, "Interpolatee (%d) size was 0 (was it initialized correctly)?",
anim->interpolant_type);
}
}

memcpy(&anim->from, aci.from, data_size);
memcpy(&anim->to, aci.to, data_size);

return anim;
}

auto advance_single_animation(Scheduled_Animation* anim) -> void {
// if not yet started, skip
if (anim->seconds_to_start > 0)
return;

// otherwise run it:
f32 perf_diff = anim->seconds_to_end - anim->seconds_to_start;
f32 perf_inside = -anim->seconds_to_start;
f64 t = 1.0 * perf_inside / perf_diff;

switch (anim->interpolation_type) {
case Interpolation_Type::Lerp: break;
case Interpolation_Type::Ease_In: {
t *= t;
} break;
case Interpolation_Type::Ease_Out: {
t = -(t * (t - 2));
} break;
case Interpolation_Type::Ease_In_And_Out: {
// NOTE(Felix): yes -- Mon Dec 7 11:47:34 2020
t = (t < 0.5) ?
+2 * t * t :
-2 * (t * (t - 2)) - 1;
} break;
case Interpolation_Type::Ease_Middle: {
// t = (t < 0.5) ?
// -2 * t * (t - 1) :
// +2 * t * (t - 1) + 1;
t = 4.0f / 5.0f * t*t*t - 6.0f/5.0f *t*t + 7.0f/5.0f * t;
} break;
default: {
// try user supported shaper
Shaper_F shaper = _shapers[(u8)anim->interpolant_type];

panic_if(shaper == nullptr,
"Shaper function for type (%d) was nullptr (was it initialized correctly)?",
anim->interpolation_type);

t = shaper(t);
}
}

switch(anim->interpolant_type) {
case Interpolant_Type::F32: {
f32 from = *((f32*)&anim->from);
f32 to = *((f32*)&anim->to);
*((f32*)anim->interpolant) = from + (to - from) * t;
} break;
default: {
// try user supported interpolator
Interpolator_F interpolator = _interpolators[(u8)anim->interpolant_type];

panic_if(interpolator == nullptr,
"Interpolator function for type (%d) was nullptr (was it initialized correctly)?",
anim->interpolant_type);

interpolator(&anim->from, t, &anim->to, anim->interpolant);
}
}
}

auto _init_action(Action* action, Action_Create_Info aci) -> void {
action->next = nullptr;
#ifdef FTB_INTERNAL_DEBUG
action->name = aci.name;
#endif
action->creation_stamp = _action_stamp_counter;

++_action_stamp_counter;
action->already_ran = false;

if (aci.param) {
if(aci.param_size > sizeof(Data_Block)) {
panic("%llu (sizeof(Data_Block)) < %u (aci.param_size)",
sizeof(Data_Block), aci.param_size);
}
action->type = Function_Type::Closure;
action->closure = aci.closure;
memcpy(&action->param, aci.param, aci.param_size);
} else {
action->type = Function_Type::Lambda;
action->lambda = aci.lambda;
memset(&action->param, 0, sizeof(Data_Block));
}

if (aci.type == Function_Type::Lambda) action->lambda = aci.lambda;
else if (aci.type == Function_Type::Closure) action->closure = aci.closure;

}

auto schedule_action(Scheduled_Action_Create_Info aci) -> Scheduled_Action* {
Scheduled_Action* scheduled_action;
if (_should_reset) {
scheduled_action = future_scheduled_actions->allocate();
} else {
scheduled_action = scheduled_actions->allocate();
}

scheduled_action->seconds_to_run = aci.seconds_to_run;
_init_action(&scheduled_action->action, aci.action);
debug_log("scheduling action %s %d", aci.action.name, scheduled_action->action.creation_stamp);
return scheduled_action;
}


auto _actually_reset() {
debug_log("HARD RESET SCHEDULER");
_should_reset = false;
active_animations->clear();
scheduled_actions->clear();
chained_actions->clear();
_animations_marked_for_deletion.clear();
_actions_marked_for_deletion.clear();

// swap ping pong
Bucket_Allocator<Scheduled_Animation>* t_active_animations = active_animations;
Bucket_Allocator<Scheduled_Action>* t_scheduled_actions = scheduled_actions;
Bucket_Allocator<Action>* t_chained_actions = chained_actions;

active_animations = future_active_animations;
scheduled_actions = future_scheduled_actions;
chained_actions = future_chained_actions;

future_active_animations = t_active_animations;
future_scheduled_actions = t_scheduled_actions;
future_chained_actions = t_chained_actions;
}

auto reset() -> void {
if (_is_iterating) {
debug_log("SOFT RESET Scheduler");
_should_reset = true;
future_active_animations->clear();
future_scheduled_actions->clear();
future_chained_actions->clear();
}
else
_actually_reset();
}

auto chain_action(Scheduled_Animation* existing_animation, Action_Create_Info aci) -> Action* {
Action* chain_action;
if (_should_reset) {
chain_action = future_chained_actions->allocate();
} else {
chain_action = chained_actions->allocate();
}

_init_action(chain_action, aci);

existing_animation->next = chain_action;
return chain_action;
}

auto chain_action(Action* existing_action, Action_Create_Info aci) -> Action* {
Action* chain_action;
if (_should_reset) {
chain_action = future_chained_actions->allocate();
} else {
chain_action = chained_actions->allocate();
}

_init_action(chain_action, aci);

existing_action->next = chain_action;
return chain_action;
}

inline auto chain_action(Action_Or_Animation aoa, Action_Create_Info aci) -> Action* {
if (aoa.type == Schedulee_Type::Action)
return chain_action(aoa.action, aci);
else if (aoa.type == Schedulee_Type::Animation)
return chain_action(aoa.animation, aci);
else if (aoa.type == Schedulee_Type::None) {
Scheduled_Action* sa = schedule_action({
.seconds_to_run = 0.0f,
.action { aci }
});
return &sa->action;
} else {
panic("unknown Schedulee type");
return nullptr;
}
}

inline auto execute_action(Action* action) -> void {
debug_log("running action %s %d", action->name, action->creation_stamp);

action->already_ran = true;
if (action->type == Function_Type::Closure) {
action->closure(action->param);
} else {
action->lambda();
}

debug_log("finished running action %s %d", action->name, action->creation_stamp);
}

auto handle_chained_action(Action* action) -> void {
debug_log("Handling chained actions");
while (action) {
debug_log("RUNNING CHAINED %s %d", action->name, action->creation_stamp);
execute_action(action);

debug_log("DELETING action %s %d", action->name, action->creation_stamp);
chained_actions->free_object(action);
action = action->next;

if (_should_stop_iterating) break;
}
debug_log("Chained actions done");
}

auto run_pending_actions_and_reset() -> void {
Auto_Array_List<Action*> pending_actions(16);

debug_log("%{color<}Running pending actions:", console_cyan);
scheduled_actions->for_each([&](Scheduled_Action* s_action) {
if (!s_action->action.already_ran) {
pending_actions.append(&s_action->action);
}
});

active_animations->for_each([&](Scheduled_Animation* s_animation) {
if (s_animation->next && !s_animation->next->already_ran) {
pending_actions.append(s_animation->next);
}
});

qsort(pending_actions.data,
pending_actions.count,
sizeof(pending_actions.data[0]),
[] (const void* a1, const void* a2) -> int {
return (*((Action**)(a1)))->creation_stamp - (*((Action**)(a2)))->creation_stamp;
});

for (auto action : pending_actions) {
execute_action(action);
}

debug_log("pending_actions done%{>color}");
_should_stop_iterating = true;
reset();
}

auto update_all(f32 delta_time) -> void {

// advance animations and collect due actions
{
active_animations->for_each([=](Scheduled_Animation* anim) {
anim->seconds_to_start -= delta_time;
anim->seconds_to_end -= delta_time;

advance_single_animation(anim);

if (anim->seconds_to_end <= 0) {
// NOTE(Felix): if already finished snap the value to its target
// and delete it
switch (anim->interpolant_type) {
case Interpolant_Type::F32: *(f32*)anim->interpolant = *(f32*)anim->to; break;
default: {
size_t interpolantee_size = _interpolatee_sizes[(u8)anim->interpolant_type];
panic_if(interpolantee_size == 0,
"Interpolatee (%d) size was 0 (was it initialized correctly)?",
anim->interpolant_type);

memcpy(anim->interpolant, anim->to, interpolantee_size);
}
}

_animations_marked_for_deletion.append(anim);
// NOTE(Felix): Don't actually run the next here, queue it
// up for after the iteration, see note below.
if (anim->next) {
_chained_actions_to_be_run_after_iteration.append(anim->next);
}
}
});
scheduled_actions->for_each([=](Scheduled_Action* action) {
action->seconds_to_run -= delta_time;
if (action->seconds_to_run <= 0) {
_chained_actions_to_be_run_after_iteration.append(&action->action);
_actions_marked_for_deletion.append(action);
}
});
}


// NOTE(Felix): The reason why we iterate over the chained actions like
// this: Imagine the following scenario: We schedule two animations
// that take the same time to run should run in parallel. So what you
// do for the first animation is put their parenting action (not the
// unparenting action) as the last action in the current game state
// and schedule their animation and then chain the unparenting action
// to the animation. The gamestate however only knows about the
// parenting action, so that the next thing that is scheduled runs
// parallel to it. For the second animation, the parenting action is
// scheduled onto the parenting action of the first animation, the
// animation is scheduled, and then also the unparenting action is
// chained to the animation. This time we set the Game_State
// last_scheduled to the unparenting action, so that no further
// animation will happen in parallel. Now imagine we chain some
// animation to the game state, that involves the animation of the
// first object. It can happen, when iterating through the animations,
// that we iterate first over the second animation, would then run
// their chained actions, in which we would animate the first object.
// This is bad, as they are not finished yet, their unparenting action
// did not run yet. Now we "solve" this by iterating breadth first
// over the actions.
if (_chained_actions_to_be_run_after_iteration.count > 1) {
qsort(_chained_actions_to_be_run_after_iteration.data,
_chained_actions_to_be_run_after_iteration.count,
sizeof(_chained_actions_to_be_run_after_iteration.data[0]),
[] (const void* a1, const void* a2) -> int {
return (*((Action**)(a1)))->creation_stamp - (*((Action**)(a2)))->creation_stamp;
});
}
// excute all due actions:
{
_is_iterating = true;
_should_stop_iterating = false;
for (Action* action : _chained_actions_to_be_run_after_iteration) {
if (action->already_ran) continue;

execute_action(action);

if (_should_stop_iterating) break;

handle_chained_action(action->next);

if (_should_stop_iterating) break;
}
_is_iterating = false;
_chained_actions_to_be_run_after_iteration.clear();
}


// NOTE(Felix): if Scheduler::reset was called in an action (happens
// when scheduling a level switch), then don't actually reset
// everyting, but only set _should_reset. Because if we would actually
// clear all the bucket allocators, we would then anyway call
// free_object on them down when we process the _marked_for_deletion
// things, so we would have stuff in the free list that is not
// actually allocated
if (_should_reset) {
_actually_reset();
} else {
// NOTE(Felix): Don't free stuff from the bucket_allocators while
// iterating over them, as this appends the pointers to the free_list,
// which then is no longer sorted, which destroys the mechanism to
// check if a bucket is active or freed, so for_each will be called
// for buckets that are no longer alive. -Felix Dec 18 2020
for (auto anim : _animations_marked_for_deletion) {
active_animations->free_object(anim);
}
for (auto action : _actions_marked_for_deletion) {
scheduled_actions->free_object(action);
}
_animations_marked_for_deletion.clear();
_actions_marked_for_deletion.clear();
}
}
}

+ 51
- 0
stacktrace.hpp Ver ficheiro

@@ -0,0 +1,51 @@
#pragma once

#include "platform.hpp"

#if defined FTB_WINDOWS
# include <dbghelp.h>
#else
# include <execinfo.h>
# include <unistd.h>
# include "stdio.h"
# include "stdlib.h"
#endif

auto print_stacktrace() -> void {
#if defined FTB_WINDOWS
printf("Stacktrace: \n");
unsigned int i;
void * stack[ 100 ];
HANDLE process;
SYMBOL_INFO * symbol;
symbol = ( SYMBOL_INFO * )calloc( sizeof( SYMBOL_INFO ) + 256 * sizeof( char ), 1 );
symbol->MaxNameLen = 255;
symbol->SizeOfStruct = sizeof( SYMBOL_INFO );

unsigned short frames;
frames = CaptureStackBackTrace( 1, 100, stack, NULL );

process = GetCurrentProcess();
SymInitialize( process, NULL, TRUE );

for( i = 0; i < frames; i++ ) {
SymFromAddr( process, ( DWORD64 )( stack[ i ] ), 0, symbol );
printf( " %3i: %s\n", frames - i - 1, symbol->Name);
}
fflush(stdout);
#else
// NOTE(Felix): Don't forget to compile with "-rdynamic"
printf("Stacktrace (this is unmagled -- sorry): \n");
char **strings;
size_t i, size;
enum Constexpr { MAX_SIZE = 1024 };
void *array[MAX_SIZE];
size = backtrace(array, MAX_SIZE);
strings = backtrace_symbols(array, size);
for (i = 0; i < size; i++)
printf(" %3lu: %s\n", size - i - 1, strings[i]);
puts("");
free(strings);

#endif
}

+ 2
- 2
tests/build.sh Ver ficheiro

@@ -4,11 +4,11 @@ pushd $SCRIPTPATH > /dev/null

# _DEBUG
# time g++ -fpermissive src/main.cpp -g -o ./bin/slime --std=c++17 || exit 1
time clang++ -D_DEBUG -D_PROFILING -fpermissive main.cpp -g -o ./ftb --std=c++17 || exit 1
time clang++ -rdynamic -D_DEBUG -D_PROFILING -fpermissive main.cpp -g -o ./ftb --std=c++17 || exit 1
# time clang++ -D_DEBUG -D_PROFILING -fpermissive cpu_info.cpp -g -o ./cpu_info --std=c++17 || exit 1

echo ""
# time valgrind --leak-check=full ./ftb
# time valgrind --track-origins=yes --leak-check=full --show-leak-kinds=all ./ftb
time ./ftb
# time ./cpu_info



+ 92
- 0
tests/main.cpp Ver ficheiro

@@ -18,6 +18,8 @@ inline bool hm_objects_match(Key a, Key b);
#include "../error.hpp"
#include "../hooks.hpp"
#include "../hashmap.hpp"
#include "../stacktrace.hpp"
#include "../scheduler.cpp"

#include "../error.hpp"

@@ -179,6 +181,7 @@ proc test_stack_array_lists() -> testresult {
sum += e;
iter++;
}

assert_equal_int(sum, 10);
assert_equal_int(iter, 4);

@@ -239,6 +242,9 @@ proc test_stack_array_lists() -> testresult {
proc test_queue() -> testresult {
Queue<int> q;
q.alloc(4);
defer {
q.dealloc();
};

assert(q.is_empty(), "queue should start empty");
assert_equal_int(q.get_count(), 0);
@@ -603,8 +609,92 @@ auto test_hooks() -> testresult {
return pass;
}

auto test_scheduler_animations() -> testresult {
using namespace Scheduler;

Scheduler::init();
defer { Scheduler::deinit(); };

f32 val = 0;

f32 from = 1;
f32 to = 2;
schedule_animation({
.seconds_to_start = 1,
.seconds_to_end = 2,
.interpolant = &val,
.interpolant_type = Interpolant_Type::F32,
.from = &from,
.to = &to,
.interpolation_type = Interpolation_Type::Lerp
});

assert_equal_f64((f64)val, 0.0);

update_all(1);
assert_equal_f64((f64)val, 1.0);

update_all(0.1);
assert_equal_int(abs(val - 1.1) < 0.001, true);

update_all(0.1);
assert_equal_int(abs(val - 1.2) < 0.001, true);

update_all(0.2);
assert_equal_int(abs(val - 1.4) < 0.001, true);

update_all(1);
assert_equal_int(abs(val - 2) < 0.001, true);

// testing custom type interpolation
enum My_Interpolant_Type : u8 {
S32
};

register_interpolator([](void* p_from, f32 t, void* p_to, void* p_interpolant) {
s32 from = *(s32*)p_from;
s32 to = *(s32*)p_to;
s32* target = (s32*)p_interpolant;
*target = from + (to - from) * t;
}, (Interpolant_Type)My_Interpolant_Type::S32, sizeof(s32));

s32 test = 0;

s32 s_from = 1;
s32 s_to = 11;
schedule_animation({
.seconds_to_start = 1,
.seconds_to_end = 2,
.interpolant = &test,
.interpolant_type = (Interpolant_Type)My_Interpolant_Type::S32,
.from = &s_from,
.to = &s_to,
.interpolation_type = Interpolation_Type::Lerp
});

assert_equal_int(test, 0);

update_all(1);
assert_equal_int(test, 1);

update_all(0.1);
assert_equal_int(test, 2);

update_all(0.1);
assert_equal_int(test, 3);

update_all(0.2);
assert_equal_int(test, 5);

update_all(1);
assert_equal_int(test, 11);

return pass;
}

s32 main(s32, char**) {
init_printer();
defer { deinit_printer(); };
testresult result;

invoke_test(test_array_lists_adding_and_removing);
@@ -615,6 +705,8 @@ s32 main(s32, char**) {
invoke_test(test_bucket_allocator);
invoke_test(test_queue);
invoke_test(test_hooks);
invoke_test(test_scheduler_animations);


return 0;
}

Carregando…
Cancelar
Guardar