|
|
|
@ -50,38 +50,46 @@ static bool operator<(const Event& left, const Event& right)
|
|
|
|
|
return std::tie(left.time, left.fifo_order) < std::tie(right.time, right.fifo_order);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// unordered_map stores each element separately as a linked list node so pointers to elements
|
|
|
|
|
// remain stable regardless of rehashes/resizing.
|
|
|
|
|
static std::unordered_map<std::string, EventType> s_event_types;
|
|
|
|
|
|
|
|
|
|
// STATE_TO_SAVE
|
|
|
|
|
// The queue is a min-heap using std::make_heap/push_heap/pop_heap.
|
|
|
|
|
// We don't use std::priority_queue because we need to be able to serialize, unserialize and
|
|
|
|
|
// erase arbitrary events (RemoveEvent()) regardless of the queue order. These aren't accomodated
|
|
|
|
|
// by the standard adaptor class.
|
|
|
|
|
static std::vector<Event> s_event_queue;
|
|
|
|
|
static u64 s_event_fifo_id;
|
|
|
|
|
static std::mutex s_ts_write_lock;
|
|
|
|
|
static Common::SPSCQueue<Event, false> s_ts_queue;
|
|
|
|
|
|
|
|
|
|
static float s_last_OC_factor;
|
|
|
|
|
static constexpr int MAX_SLICE_LENGTH = 20000;
|
|
|
|
|
|
|
|
|
|
static s64 s_idled_cycles;
|
|
|
|
|
static u32 s_fake_dec_start_value;
|
|
|
|
|
static u64 s_fake_dec_start_ticks;
|
|
|
|
|
struct CoreTimingState::Data
|
|
|
|
|
{
|
|
|
|
|
// unordered_map stores each element separately as a linked list node so pointers to elements
|
|
|
|
|
// remain stable regardless of rehashes/resizing.
|
|
|
|
|
std::unordered_map<std::string, EventType> event_types;
|
|
|
|
|
|
|
|
|
|
// Are we in a function that has been called from Advance()
|
|
|
|
|
static bool s_is_global_timer_sane;
|
|
|
|
|
// STATE_TO_SAVE
|
|
|
|
|
// The queue is a min-heap using std::make_heap/push_heap/pop_heap.
|
|
|
|
|
// We don't use std::priority_queue because we need to be able to serialize, unserialize and
|
|
|
|
|
// erase arbitrary events (RemoveEvent()) regardless of the queue order. These aren't accomodated
|
|
|
|
|
// by the standard adaptor class.
|
|
|
|
|
std::vector<Event> event_queue;
|
|
|
|
|
u64 event_fifo_id;
|
|
|
|
|
std::mutex ts_write_lock;
|
|
|
|
|
Common::SPSCQueue<Event, false> ts_queue;
|
|
|
|
|
|
|
|
|
|
Globals g;
|
|
|
|
|
float last_oc_factor;
|
|
|
|
|
|
|
|
|
|
static EventType* s_ev_lost = nullptr;
|
|
|
|
|
s64 idled_cycles;
|
|
|
|
|
u32 fake_dec_start_value;
|
|
|
|
|
u64 fake_dec_start_ticks;
|
|
|
|
|
|
|
|
|
|
static size_t s_registered_config_callback_id;
|
|
|
|
|
static float s_config_OC_factor;
|
|
|
|
|
static float s_config_OC_inv_factor;
|
|
|
|
|
static bool s_config_sync_on_skip_idle;
|
|
|
|
|
// Are we in a function that has been called from Advance()
|
|
|
|
|
bool is_global_timer_sane;
|
|
|
|
|
|
|
|
|
|
EventType* ev_lost = nullptr;
|
|
|
|
|
|
|
|
|
|
size_t registered_config_callback_id;
|
|
|
|
|
float config_oc_factor;
|
|
|
|
|
float config_oc_inv_factor;
|
|
|
|
|
bool config_sync_on_skip_idle;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
CoreTimingState::CoreTimingState() : m_data(std::make_unique<Data>())
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CoreTimingState::~CoreTimingState() = default;
|
|
|
|
|
|
|
|
|
|
static void EmptyTimedCallback(Core::System& system, u64 userdata, s64 cyclesLate)
|
|
|
|
|
{
|
|
|
|
@ -94,26 +102,28 @@ static void EmptyTimedCallback(Core::System& system, u64 userdata, s64 cyclesLat
|
|
|
|
|
//
|
|
|
|
|
// Technically it might be more accurate to call this changing the IPC instead of the CPU speed,
|
|
|
|
|
// but the effect is largely the same.
|
|
|
|
|
static int DowncountToCycles(int downcount)
|
|
|
|
|
static int DowncountToCycles(CoreTiming::Globals& g, int downcount)
|
|
|
|
|
{
|
|
|
|
|
return static_cast<int>(downcount * g.last_OC_factor_inverted);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int CyclesToDowncount(int cycles)
|
|
|
|
|
static int CyclesToDowncount(CoreTiming::CoreTimingState::Data& state, int cycles)
|
|
|
|
|
{
|
|
|
|
|
return static_cast<int>(cycles * s_last_OC_factor);
|
|
|
|
|
return static_cast<int>(cycles * state.last_oc_factor);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
EventType* RegisterEvent(const std::string& name, TimedCallback callback)
|
|
|
|
|
{
|
|
|
|
|
auto& state = Core::System::GetInstance().GetCoreTimingState().GetData();
|
|
|
|
|
|
|
|
|
|
// check for existing type with same name.
|
|
|
|
|
// we want event type names to remain unique so that we can use them for serialization.
|
|
|
|
|
ASSERT_MSG(POWERPC, s_event_types.find(name) == s_event_types.end(),
|
|
|
|
|
ASSERT_MSG(POWERPC, state.event_types.find(name) == state.event_types.end(),
|
|
|
|
|
"CoreTiming Event \"{}\" is already registered. Events should only be registered "
|
|
|
|
|
"during Init to avoid breaking save states.",
|
|
|
|
|
name);
|
|
|
|
|
|
|
|
|
|
auto info = s_event_types.emplace(name, EventType{callback, nullptr});
|
|
|
|
|
auto info = state.event_types.emplace(name, EventType{callback, nullptr});
|
|
|
|
|
EventType* event_type = &info.first->second;
|
|
|
|
|
event_type->name = &info.first->first;
|
|
|
|
|
return event_type;
|
|
|
|
@ -121,68 +131,80 @@ EventType* RegisterEvent(const std::string& name, TimedCallback callback)
|
|
|
|
|
|
|
|
|
|
void UnregisterAllEvents()
|
|
|
|
|
{
|
|
|
|
|
ASSERT_MSG(POWERPC, s_event_queue.empty(), "Cannot unregister events with events pending");
|
|
|
|
|
s_event_types.clear();
|
|
|
|
|
auto& state = Core::System::GetInstance().GetCoreTimingState().GetData();
|
|
|
|
|
|
|
|
|
|
ASSERT_MSG(POWERPC, state.event_queue.empty(), "Cannot unregister events with events pending");
|
|
|
|
|
state.event_types.clear();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Init()
|
|
|
|
|
{
|
|
|
|
|
s_registered_config_callback_id =
|
|
|
|
|
auto& system = Core::System::GetInstance();
|
|
|
|
|
auto& state = system.GetCoreTimingState().GetData();
|
|
|
|
|
auto& g = system.GetCoreTimingGlobals();
|
|
|
|
|
|
|
|
|
|
state.registered_config_callback_id =
|
|
|
|
|
Config::AddConfigChangedCallback([]() { Core::RunAsCPUThread([]() { RefreshConfig(); }); });
|
|
|
|
|
RefreshConfig();
|
|
|
|
|
|
|
|
|
|
s_last_OC_factor = s_config_OC_factor;
|
|
|
|
|
g.last_OC_factor_inverted = s_config_OC_inv_factor;
|
|
|
|
|
PowerPC::ppcState.downcount = CyclesToDowncount(MAX_SLICE_LENGTH);
|
|
|
|
|
state.last_oc_factor = state.config_oc_factor;
|
|
|
|
|
g.last_OC_factor_inverted = state.config_oc_inv_factor;
|
|
|
|
|
PowerPC::ppcState.downcount = CyclesToDowncount(state, MAX_SLICE_LENGTH);
|
|
|
|
|
g.slice_length = MAX_SLICE_LENGTH;
|
|
|
|
|
g.global_timer = 0;
|
|
|
|
|
s_idled_cycles = 0;
|
|
|
|
|
state.idled_cycles = 0;
|
|
|
|
|
|
|
|
|
|
// The time between CoreTiming being intialized and the first call to Advance() is considered
|
|
|
|
|
// the slice boundary between slice -1 and slice 0. Dispatcher loops must call Advance() before
|
|
|
|
|
// executing the first PPC cycle of each slice to prepare the slice length and downcount for
|
|
|
|
|
// that slice.
|
|
|
|
|
s_is_global_timer_sane = true;
|
|
|
|
|
state.is_global_timer_sane = true;
|
|
|
|
|
|
|
|
|
|
s_event_fifo_id = 0;
|
|
|
|
|
s_ev_lost = RegisterEvent("_lost_event", &EmptyTimedCallback);
|
|
|
|
|
state.event_fifo_id = 0;
|
|
|
|
|
state.ev_lost = RegisterEvent("_lost_event", &EmptyTimedCallback);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Shutdown()
|
|
|
|
|
{
|
|
|
|
|
std::lock_guard lk(s_ts_write_lock);
|
|
|
|
|
auto& state = Core::System::GetInstance().GetCoreTimingState().GetData();
|
|
|
|
|
std::lock_guard lk(state.ts_write_lock);
|
|
|
|
|
MoveEvents();
|
|
|
|
|
ClearPendingEvents();
|
|
|
|
|
UnregisterAllEvents();
|
|
|
|
|
Config::RemoveConfigChangedCallback(s_registered_config_callback_id);
|
|
|
|
|
Config::RemoveConfigChangedCallback(state.registered_config_callback_id);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void RefreshConfig()
|
|
|
|
|
{
|
|
|
|
|
s_config_OC_factor =
|
|
|
|
|
auto& state = Core::System::GetInstance().GetCoreTimingState().GetData();
|
|
|
|
|
state.config_oc_factor =
|
|
|
|
|
Config::Get(Config::MAIN_OVERCLOCK_ENABLE) ? Config::Get(Config::MAIN_OVERCLOCK) : 1.0f;
|
|
|
|
|
s_config_OC_inv_factor = 1.0f / s_config_OC_factor;
|
|
|
|
|
s_config_sync_on_skip_idle = Config::Get(Config::MAIN_SYNC_ON_SKIP_IDLE);
|
|
|
|
|
state.config_oc_inv_factor = 1.0f / state.config_oc_factor;
|
|
|
|
|
state.config_sync_on_skip_idle = Config::Get(Config::MAIN_SYNC_ON_SKIP_IDLE);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void DoState(PointerWrap& p)
|
|
|
|
|
{
|
|
|
|
|
std::lock_guard lk(s_ts_write_lock);
|
|
|
|
|
auto& system = Core::System::GetInstance();
|
|
|
|
|
auto& state = system.GetCoreTimingState().GetData();
|
|
|
|
|
auto& g = system.GetCoreTimingGlobals();
|
|
|
|
|
|
|
|
|
|
std::lock_guard lk(state.ts_write_lock);
|
|
|
|
|
p.Do(g.slice_length);
|
|
|
|
|
p.Do(g.global_timer);
|
|
|
|
|
p.Do(s_idled_cycles);
|
|
|
|
|
p.Do(s_fake_dec_start_value);
|
|
|
|
|
p.Do(s_fake_dec_start_ticks);
|
|
|
|
|
p.Do(state.idled_cycles);
|
|
|
|
|
p.Do(state.fake_dec_start_value);
|
|
|
|
|
p.Do(state.fake_dec_start_ticks);
|
|
|
|
|
p.Do(g.fake_TB_start_value);
|
|
|
|
|
p.Do(g.fake_TB_start_ticks);
|
|
|
|
|
p.Do(s_last_OC_factor);
|
|
|
|
|
g.last_OC_factor_inverted = 1.0f / s_last_OC_factor;
|
|
|
|
|
p.Do(s_event_fifo_id);
|
|
|
|
|
p.Do(state.last_oc_factor);
|
|
|
|
|
g.last_OC_factor_inverted = 1.0f / state.last_oc_factor;
|
|
|
|
|
p.Do(state.event_fifo_id);
|
|
|
|
|
|
|
|
|
|
p.DoMarker("CoreTimingData");
|
|
|
|
|
|
|
|
|
|
MoveEvents();
|
|
|
|
|
p.DoEachElement(s_event_queue, [](PointerWrap& pw, Event& ev) {
|
|
|
|
|
p.DoEachElement(state.event_queue, [&state](PointerWrap& pw, Event& ev) {
|
|
|
|
|
pw.Do(ev.time);
|
|
|
|
|
pw.Do(ev.fifo_order);
|
|
|
|
|
|
|
|
|
@ -199,8 +221,8 @@ void DoState(PointerWrap& p)
|
|
|
|
|
pw.Do(name);
|
|
|
|
|
if (pw.IsReadMode())
|
|
|
|
|
{
|
|
|
|
|
auto itr = s_event_types.find(name);
|
|
|
|
|
if (itr != s_event_types.end())
|
|
|
|
|
auto itr = state.event_types.find(name);
|
|
|
|
|
if (itr != state.event_types.end())
|
|
|
|
|
{
|
|
|
|
|
ev.type = &itr->second;
|
|
|
|
|
}
|
|
|
|
@ -209,7 +231,7 @@ void DoState(PointerWrap& p)
|
|
|
|
|
WARN_LOG_FMT(POWERPC,
|
|
|
|
|
"Lost event from savestate because its type, \"{}\", has not been registered.",
|
|
|
|
|
name);
|
|
|
|
|
ev.type = s_ev_lost;
|
|
|
|
|
ev.type = state.ev_lost;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
@ -219,17 +241,21 @@ void DoState(PointerWrap& p)
|
|
|
|
|
// The exact layout of the heap in memory is implementation defined, therefore it is platform
|
|
|
|
|
// and library version specific.
|
|
|
|
|
if (p.IsReadMode())
|
|
|
|
|
std::make_heap(s_event_queue.begin(), s_event_queue.end(), std::greater<Event>());
|
|
|
|
|
std::make_heap(state.event_queue.begin(), state.event_queue.end(), std::greater<Event>());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// This should only be called from the CPU thread. If you are calling
|
|
|
|
|
// it from any other thread, you are doing something evil
|
|
|
|
|
u64 GetTicks()
|
|
|
|
|
{
|
|
|
|
|
auto& system = Core::System::GetInstance();
|
|
|
|
|
auto& state = system.GetCoreTimingState().GetData();
|
|
|
|
|
auto& g = system.GetCoreTimingGlobals();
|
|
|
|
|
|
|
|
|
|
u64 ticks = static_cast<u64>(g.global_timer);
|
|
|
|
|
if (!s_is_global_timer_sane)
|
|
|
|
|
if (!state.is_global_timer_sane)
|
|
|
|
|
{
|
|
|
|
|
int downcount = DowncountToCycles(PowerPC::ppcState.downcount);
|
|
|
|
|
int downcount = DowncountToCycles(g, PowerPC::ppcState.downcount);
|
|
|
|
|
ticks += g.slice_length - downcount;
|
|
|
|
|
}
|
|
|
|
|
return ticks;
|
|
|
|
@ -237,18 +263,24 @@ u64 GetTicks()
|
|
|
|
|
|
|
|
|
|
u64 GetIdleTicks()
|
|
|
|
|
{
|
|
|
|
|
return static_cast<u64>(s_idled_cycles);
|
|
|
|
|
auto& state = Core::System::GetInstance().GetCoreTimingState().GetData();
|
|
|
|
|
return static_cast<u64>(state.idled_cycles);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ClearPendingEvents()
|
|
|
|
|
{
|
|
|
|
|
s_event_queue.clear();
|
|
|
|
|
auto& state = Core::System::GetInstance().GetCoreTimingState().GetData();
|
|
|
|
|
state.event_queue.clear();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ScheduleEvent(s64 cycles_into_future, EventType* event_type, u64 userdata, FromThread from)
|
|
|
|
|
{
|
|
|
|
|
ASSERT_MSG(POWERPC, event_type, "Event type is nullptr, will crash now.");
|
|
|
|
|
|
|
|
|
|
auto& system = Core::System::GetInstance();
|
|
|
|
|
auto& state = system.GetCoreTimingState().GetData();
|
|
|
|
|
auto& g = system.GetCoreTimingGlobals();
|
|
|
|
|
|
|
|
|
|
bool from_cpu_thread;
|
|
|
|
|
if (from == FromThread::ANY)
|
|
|
|
|
{
|
|
|
|
@ -267,11 +299,11 @@ void ScheduleEvent(s64 cycles_into_future, EventType* event_type, u64 userdata,
|
|
|
|
|
s64 timeout = GetTicks() + cycles_into_future;
|
|
|
|
|
|
|
|
|
|
// If this event needs to be scheduled before the next advance(), force one early
|
|
|
|
|
if (!s_is_global_timer_sane)
|
|
|
|
|
if (!state.is_global_timer_sane)
|
|
|
|
|
ForceExceptionCheck(cycles_into_future);
|
|
|
|
|
|
|
|
|
|
s_event_queue.emplace_back(Event{timeout, s_event_fifo_id++, userdata, event_type});
|
|
|
|
|
std::push_heap(s_event_queue.begin(), s_event_queue.end(), std::greater<Event>());
|
|
|
|
|
state.event_queue.emplace_back(Event{timeout, state.event_fifo_id++, userdata, event_type});
|
|
|
|
|
std::push_heap(state.event_queue.begin(), state.event_queue.end(), std::greater<Event>());
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
@ -283,21 +315,23 @@ void ScheduleEvent(s64 cycles_into_future, EventType* event_type, u64 userdata,
|
|
|
|
|
*event_type->name);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::lock_guard lk(s_ts_write_lock);
|
|
|
|
|
s_ts_queue.Push(Event{g.global_timer + cycles_into_future, 0, userdata, event_type});
|
|
|
|
|
std::lock_guard lk(state.ts_write_lock);
|
|
|
|
|
state.ts_queue.Push(Event{g.global_timer + cycles_into_future, 0, userdata, event_type});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void RemoveEvent(EventType* event_type)
|
|
|
|
|
{
|
|
|
|
|
auto itr = std::remove_if(s_event_queue.begin(), s_event_queue.end(),
|
|
|
|
|
auto& state = Core::System::GetInstance().GetCoreTimingState().GetData();
|
|
|
|
|
|
|
|
|
|
auto itr = std::remove_if(state.event_queue.begin(), state.event_queue.end(),
|
|
|
|
|
[&](const Event& e) { return e.type == event_type; });
|
|
|
|
|
|
|
|
|
|
// Removing random items breaks the invariant so we have to re-establish it.
|
|
|
|
|
if (itr != s_event_queue.end())
|
|
|
|
|
if (itr != state.event_queue.end())
|
|
|
|
|
{
|
|
|
|
|
s_event_queue.erase(itr, s_event_queue.end());
|
|
|
|
|
std::make_heap(s_event_queue.begin(), s_event_queue.end(), std::greater<Event>());
|
|
|
|
|
state.event_queue.erase(itr, state.event_queue.end());
|
|
|
|
|
std::make_heap(state.event_queue.begin(), state.event_queue.end(), std::greater<Event>());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -309,56 +343,65 @@ void RemoveAllEvents(EventType* event_type)
|
|
|
|
|
|
|
|
|
|
void ForceExceptionCheck(s64 cycles)
|
|
|
|
|
{
|
|
|
|
|
auto& system = Core::System::GetInstance();
|
|
|
|
|
auto& state = system.GetCoreTimingState().GetData();
|
|
|
|
|
auto& g = system.GetCoreTimingGlobals();
|
|
|
|
|
|
|
|
|
|
cycles = std::max<s64>(0, cycles);
|
|
|
|
|
if (DowncountToCycles(PowerPC::ppcState.downcount) > cycles)
|
|
|
|
|
if (DowncountToCycles(g, PowerPC::ppcState.downcount) > cycles)
|
|
|
|
|
{
|
|
|
|
|
// downcount is always (much) smaller than MAX_INT so we can safely cast cycles to an int here.
|
|
|
|
|
// Account for cycles already executed by adjusting the g.slice_length
|
|
|
|
|
g.slice_length -= DowncountToCycles(PowerPC::ppcState.downcount) - static_cast<int>(cycles);
|
|
|
|
|
PowerPC::ppcState.downcount = CyclesToDowncount(static_cast<int>(cycles));
|
|
|
|
|
g.slice_length -= DowncountToCycles(g, PowerPC::ppcState.downcount) - static_cast<int>(cycles);
|
|
|
|
|
PowerPC::ppcState.downcount = CyclesToDowncount(state, static_cast<int>(cycles));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void MoveEvents()
|
|
|
|
|
{
|
|
|
|
|
for (Event ev; s_ts_queue.Pop(ev);)
|
|
|
|
|
auto& state = Core::System::GetInstance().GetCoreTimingState().GetData();
|
|
|
|
|
for (Event ev; state.ts_queue.Pop(ev);)
|
|
|
|
|
{
|
|
|
|
|
ev.fifo_order = s_event_fifo_id++;
|
|
|
|
|
s_event_queue.emplace_back(std::move(ev));
|
|
|
|
|
std::push_heap(s_event_queue.begin(), s_event_queue.end(), std::greater<Event>());
|
|
|
|
|
ev.fifo_order = state.event_fifo_id++;
|
|
|
|
|
state.event_queue.emplace_back(std::move(ev));
|
|
|
|
|
std::push_heap(state.event_queue.begin(), state.event_queue.end(), std::greater<Event>());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Advance()
|
|
|
|
|
{
|
|
|
|
|
auto& system = Core::System::GetInstance();
|
|
|
|
|
auto& state = system.GetCoreTimingState().GetData();
|
|
|
|
|
auto& g = system.GetCoreTimingGlobals();
|
|
|
|
|
|
|
|
|
|
MoveEvents();
|
|
|
|
|
|
|
|
|
|
int cyclesExecuted = g.slice_length - DowncountToCycles(PowerPC::ppcState.downcount);
|
|
|
|
|
int cyclesExecuted = g.slice_length - DowncountToCycles(g, PowerPC::ppcState.downcount);
|
|
|
|
|
g.global_timer += cyclesExecuted;
|
|
|
|
|
s_last_OC_factor = s_config_OC_factor;
|
|
|
|
|
g.last_OC_factor_inverted = s_config_OC_inv_factor;
|
|
|
|
|
state.last_oc_factor = state.config_oc_factor;
|
|
|
|
|
g.last_OC_factor_inverted = state.config_oc_inv_factor;
|
|
|
|
|
g.slice_length = MAX_SLICE_LENGTH;
|
|
|
|
|
|
|
|
|
|
s_is_global_timer_sane = true;
|
|
|
|
|
state.is_global_timer_sane = true;
|
|
|
|
|
|
|
|
|
|
while (!s_event_queue.empty() && s_event_queue.front().time <= g.global_timer)
|
|
|
|
|
while (!state.event_queue.empty() && state.event_queue.front().time <= g.global_timer)
|
|
|
|
|
{
|
|
|
|
|
Event evt = std::move(s_event_queue.front());
|
|
|
|
|
std::pop_heap(s_event_queue.begin(), s_event_queue.end(), std::greater<Event>());
|
|
|
|
|
s_event_queue.pop_back();
|
|
|
|
|
evt.type->callback(Core::System::GetInstance(), evt.userdata, g.global_timer - evt.time);
|
|
|
|
|
Event evt = std::move(state.event_queue.front());
|
|
|
|
|
std::pop_heap(state.event_queue.begin(), state.event_queue.end(), std::greater<Event>());
|
|
|
|
|
state.event_queue.pop_back();
|
|
|
|
|
evt.type->callback(system, evt.userdata, g.global_timer - evt.time);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
s_is_global_timer_sane = false;
|
|
|
|
|
state.is_global_timer_sane = false;
|
|
|
|
|
|
|
|
|
|
// Still events left (scheduled in the future)
|
|
|
|
|
if (!s_event_queue.empty())
|
|
|
|
|
if (!state.event_queue.empty())
|
|
|
|
|
{
|
|
|
|
|
g.slice_length = static_cast<int>(
|
|
|
|
|
std::min<s64>(s_event_queue.front().time - g.global_timer, MAX_SLICE_LENGTH));
|
|
|
|
|
std::min<s64>(state.event_queue.front().time - g.global_timer, MAX_SLICE_LENGTH));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
PowerPC::ppcState.downcount = CyclesToDowncount(g.slice_length);
|
|
|
|
|
PowerPC::ppcState.downcount = CyclesToDowncount(state, g.slice_length);
|
|
|
|
|
|
|
|
|
|
// Check for any external exceptions.
|
|
|
|
|
// It's important to do this after processing events otherwise any exceptions will be delayed
|
|
|
|
@ -369,7 +412,11 @@ void Advance()
|
|
|
|
|
|
|
|
|
|
void LogPendingEvents()
|
|
|
|
|
{
|
|
|
|
|
auto clone = s_event_queue;
|
|
|
|
|
auto& system = Core::System::GetInstance();
|
|
|
|
|
auto& state = system.GetCoreTimingState().GetData();
|
|
|
|
|
auto& g = system.GetCoreTimingGlobals();
|
|
|
|
|
|
|
|
|
|
auto clone = state.event_queue;
|
|
|
|
|
std::sort(clone.begin(), clone.end());
|
|
|
|
|
for (const Event& ev : clone)
|
|
|
|
|
{
|
|
|
|
@ -381,7 +428,11 @@ void LogPendingEvents()
|
|
|
|
|
// Should only be called from the CPU thread after the PPC clock has changed
|
|
|
|
|
void AdjustEventQueueTimes(u32 new_ppc_clock, u32 old_ppc_clock)
|
|
|
|
|
{
|
|
|
|
|
for (Event& ev : s_event_queue)
|
|
|
|
|
auto& system = Core::System::GetInstance();
|
|
|
|
|
auto& state = system.GetCoreTimingState().GetData();
|
|
|
|
|
auto& g = system.GetCoreTimingGlobals();
|
|
|
|
|
|
|
|
|
|
for (Event& ev : state.event_queue)
|
|
|
|
|
{
|
|
|
|
|
const s64 ticks = (ev.time - g.global_timer) * new_ppc_clock / old_ppc_clock;
|
|
|
|
|
ev.time = g.global_timer + ticks;
|
|
|
|
@ -390,7 +441,11 @@ void AdjustEventQueueTimes(u32 new_ppc_clock, u32 old_ppc_clock)
|
|
|
|
|
|
|
|
|
|
void Idle()
|
|
|
|
|
{
|
|
|
|
|
if (s_config_sync_on_skip_idle)
|
|
|
|
|
auto& system = Core::System::GetInstance();
|
|
|
|
|
auto& state = system.GetCoreTimingState().GetData();
|
|
|
|
|
auto& g = system.GetCoreTimingGlobals();
|
|
|
|
|
|
|
|
|
|
if (state.config_sync_on_skip_idle)
|
|
|
|
|
{
|
|
|
|
|
// When the FIFO is processing data we must not advance because in this way
|
|
|
|
|
// the VI will be desynchronized. So, We are waiting until the FIFO finish and
|
|
|
|
@ -399,16 +454,18 @@ void Idle()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
PowerPC::UpdatePerformanceMonitor(PowerPC::ppcState.downcount, 0, 0);
|
|
|
|
|
s_idled_cycles += DowncountToCycles(PowerPC::ppcState.downcount);
|
|
|
|
|
state.idled_cycles += DowncountToCycles(g, PowerPC::ppcState.downcount);
|
|
|
|
|
PowerPC::ppcState.downcount = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string GetScheduledEventsSummary()
|
|
|
|
|
{
|
|
|
|
|
auto& state = Core::System::GetInstance().GetCoreTimingState().GetData();
|
|
|
|
|
|
|
|
|
|
std::string text = "Scheduled events\n";
|
|
|
|
|
text.reserve(1000);
|
|
|
|
|
|
|
|
|
|
auto clone = s_event_queue;
|
|
|
|
|
auto clone = state.event_queue;
|
|
|
|
|
std::sort(clone.begin(), clone.end());
|
|
|
|
|
for (const Event& ev : clone)
|
|
|
|
|
{
|
|
|
|
@ -419,41 +476,49 @@ std::string GetScheduledEventsSummary()
|
|
|
|
|
|
|
|
|
|
u32 GetFakeDecStartValue()
|
|
|
|
|
{
|
|
|
|
|
return s_fake_dec_start_value;
|
|
|
|
|
auto& state = Core::System::GetInstance().GetCoreTimingState().GetData();
|
|
|
|
|
return state.fake_dec_start_value;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SetFakeDecStartValue(u32 val)
|
|
|
|
|
{
|
|
|
|
|
s_fake_dec_start_value = val;
|
|
|
|
|
auto& state = Core::System::GetInstance().GetCoreTimingState().GetData();
|
|
|
|
|
state.fake_dec_start_value = val;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
u64 GetFakeDecStartTicks()
|
|
|
|
|
{
|
|
|
|
|
return s_fake_dec_start_ticks;
|
|
|
|
|
auto& state = Core::System::GetInstance().GetCoreTimingState().GetData();
|
|
|
|
|
return state.fake_dec_start_ticks;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SetFakeDecStartTicks(u64 val)
|
|
|
|
|
{
|
|
|
|
|
s_fake_dec_start_ticks = val;
|
|
|
|
|
auto& state = Core::System::GetInstance().GetCoreTimingState().GetData();
|
|
|
|
|
state.fake_dec_start_ticks = val;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
u64 GetFakeTBStartValue()
|
|
|
|
|
{
|
|
|
|
|
auto& g = Core::System::GetInstance().GetCoreTimingGlobals();
|
|
|
|
|
return g.fake_TB_start_value;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SetFakeTBStartValue(u64 val)
|
|
|
|
|
{
|
|
|
|
|
auto& g = Core::System::GetInstance().GetCoreTimingGlobals();
|
|
|
|
|
g.fake_TB_start_value = val;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
u64 GetFakeTBStartTicks()
|
|
|
|
|
{
|
|
|
|
|
auto& g = Core::System::GetInstance().GetCoreTimingGlobals();
|
|
|
|
|
return g.fake_TB_start_ticks;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SetFakeTBStartTicks(u64 val)
|
|
|
|
|
{
|
|
|
|
|
auto& g = Core::System::GetInstance().GetCoreTimingGlobals();
|
|
|
|
|
g.fake_TB_start_ticks = val;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|