|
|
@@ -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(); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |