scheduler in ftb
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
|
#pragma once
|
||||||
#include "arraylist.hpp"
|
#include "arraylist.hpp"
|
||||||
#include "types.hpp"
|
#include "types.hpp"
|
||||||
|
|
||||||
|
|||||||
@@ -68,10 +68,10 @@ struct Hook {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct __System_Shutdown_Hook : Hook {
|
// struct __System_Shutdown_Hook : Hook {
|
||||||
void operator()() = delete;
|
// void operator()() = delete;
|
||||||
~__System_Shutdown_Hook() {
|
// ~__System_Shutdown_Hook() {
|
||||||
Hook::operator()();
|
// Hook::operator()();
|
||||||
lambdas.dealloc();
|
// lambdas.dealloc();
|
||||||
}
|
// }
|
||||||
} system_shutdown_hook;
|
// } system_shutdown_hook;
|
||||||
|
|||||||
+46
@@ -1,4 +1,11 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
#include "platform.hpp"
|
||||||
|
|
||||||
|
#ifdef FTB_WINDOWS
|
||||||
|
#else
|
||||||
|
# include <signal.h> // for sigtrap
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
|
||||||
#define proc auto
|
#define proc auto
|
||||||
@@ -131,3 +138,42 @@ else
|
|||||||
call2(var);
|
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
|
||||||
|
|||||||
@@ -500,12 +500,6 @@ void init_printer() {
|
|||||||
printer_map.alloc();
|
printer_map.alloc();
|
||||||
type_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("spaces", print_spaces, Printer_Function_Type::_32b);
|
||||||
register_printer("u32", print_u32, Printer_Function_Type::_32b);
|
register_printer("u32", print_u32, Printer_Function_Type::_32b);
|
||||||
register_printer("u64", print_u64, Printer_Function_Type::_64b);
|
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("->Str", print_Str, Printer_Function_Type::_ptr);
|
||||||
register_printer("->char_line", print_str_line, 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,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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
@@ -4,11 +4,11 @@ pushd $SCRIPTPATH > /dev/null
|
|||||||
|
|
||||||
# _DEBUG
|
# _DEBUG
|
||||||
# time g++ -fpermissive src/main.cpp -g -o ./bin/slime --std=c++17 || exit 1
|
# 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
|
# time clang++ -D_DEBUG -D_PROFILING -fpermissive cpu_info.cpp -g -o ./cpu_info --std=c++17 || exit 1
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
# time valgrind --leak-check=full ./ftb
|
# time valgrind --track-origins=yes --leak-check=full --show-leak-kinds=all ./ftb
|
||||||
time ./ftb
|
time ./ftb
|
||||||
# time ./cpu_info
|
# time ./cpu_info
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ inline bool hm_objects_match(Key a, Key b);
|
|||||||
#include "../error.hpp"
|
#include "../error.hpp"
|
||||||
#include "../hooks.hpp"
|
#include "../hooks.hpp"
|
||||||
#include "../hashmap.hpp"
|
#include "../hashmap.hpp"
|
||||||
|
#include "../stacktrace.hpp"
|
||||||
|
#include "../scheduler.cpp"
|
||||||
|
|
||||||
#include "../error.hpp"
|
#include "../error.hpp"
|
||||||
|
|
||||||
@@ -179,6 +181,7 @@ proc test_stack_array_lists() -> testresult {
|
|||||||
sum += e;
|
sum += e;
|
||||||
iter++;
|
iter++;
|
||||||
}
|
}
|
||||||
|
|
||||||
assert_equal_int(sum, 10);
|
assert_equal_int(sum, 10);
|
||||||
assert_equal_int(iter, 4);
|
assert_equal_int(iter, 4);
|
||||||
|
|
||||||
@@ -239,6 +242,9 @@ proc test_stack_array_lists() -> testresult {
|
|||||||
proc test_queue() -> testresult {
|
proc test_queue() -> testresult {
|
||||||
Queue<int> q;
|
Queue<int> q;
|
||||||
q.alloc(4);
|
q.alloc(4);
|
||||||
|
defer {
|
||||||
|
q.dealloc();
|
||||||
|
};
|
||||||
|
|
||||||
assert(q.is_empty(), "queue should start empty");
|
assert(q.is_empty(), "queue should start empty");
|
||||||
assert_equal_int(q.get_count(), 0);
|
assert_equal_int(q.get_count(), 0);
|
||||||
@@ -603,8 +609,92 @@ auto test_hooks() -> testresult {
|
|||||||
return pass;
|
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**) {
|
s32 main(s32, char**) {
|
||||||
init_printer();
|
init_printer();
|
||||||
|
defer { deinit_printer(); };
|
||||||
testresult result;
|
testresult result;
|
||||||
|
|
||||||
invoke_test(test_array_lists_adding_and_removing);
|
invoke_test(test_array_lists_adding_and_removing);
|
||||||
@@ -615,6 +705,8 @@ s32 main(s32, char**) {
|
|||||||
invoke_test(test_bucket_allocator);
|
invoke_test(test_bucket_allocator);
|
||||||
invoke_test(test_queue);
|
invoke_test(test_queue);
|
||||||
invoke_test(test_hooks);
|
invoke_test(test_hooks);
|
||||||
|
invoke_test(test_scheduler_animations);
|
||||||
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user