summaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
author Aaron Giles <aaron@aarongiles.com>2021-03-22 01:33:19 -0700
committer Aaron Giles <aaron@aarongiles.com>2021-03-22 01:33:19 -0700
commitebf3216c0b21a9900ffc8c1ead690484e162bdab (patch)
treec968bfc90ed05259de74b89e3dd14ba33a598bee
parentaaed9a2a6e2b202b3a25ed40463771cd475ea6f1 (diff)
Timer streamlining. Maintain timers in two lists (active/inactive). Fix several situations where timers would be inserted/removed unnecessarily. Moved basetime_relative class into the device scheduler.
-rw-r--r--src/emu/diexec.h2
-rw-r--r--src/emu/schedule.cpp783
-rw-r--r--src/emu/schedule.h197
3 files changed, 649 insertions, 333 deletions
diff --git a/src/emu/diexec.h b/src/emu/diexec.h
index 3c02e898b0c..1081fee6b52 100644
--- a/src/emu/diexec.h
+++ b/src/emu/diexec.h
@@ -306,7 +306,7 @@ private:
// clock and timing information
u64 m_totalcycles; // total device cycles executed
- basetime_relative m_localtime; // local time, relative to the timer system's global time
+ device_scheduler::basetime_relative m_localtime; // local time, relative to the scheduler's base
u32 m_cycles_per_second; // cycles per second, adjusted for multipliers
attoseconds_t m_attoseconds_per_cycle; // attoseconds per adjusted clock cycle
diff --git a/src/emu/schedule.cpp b/src/emu/schedule.cpp
index 72f04d29048..87302978736 100644
--- a/src/emu/schedule.cpp
+++ b/src/emu/schedule.cpp
@@ -11,7 +11,6 @@
#include "emu.h"
#include "debugger.h"
-static attotime m_timer_next_basetime;
//**************************************************************************
// DEBUGGING
@@ -23,18 +22,22 @@ static attotime m_timer_next_basetime;
#define PRECISION
-
//**************************************************************************
-// CONSTANTS
+// GLOBAL HELPERS
//**************************************************************************
-// internal trigger IDs
-enum
+//-------------------------------------------------
+// dummy_delegate - a global empty delegate that
+// can substitute for a null function pointer, if
+// one is provided; this saves us a check in the
+// innermost timer expiration loop
+//-------------------------------------------------
+
+static void dummy_delegate(void *, s32)
{
- TRIGGER_INT = -2000,
- TRIGGER_YIELDTIME = -3000,
- TRIGGER_SUSPENDTIME = -4000
-};
+}
+
+static timer_expired_delegate const s_dummy_delegate(FUNC(dummy_delegate));
@@ -46,19 +49,10 @@ enum
// emu_timer - constructor
//-------------------------------------------------
-emu_timer::emu_timer() :
- m_machine(nullptr),
- m_next(nullptr),
- m_prev(nullptr),
- m_param(0),
- m_ptr(nullptr),
- m_enabled(false),
- m_temporary(false),
- m_period(attotime::zero),
- m_start(attotime::zero),
- m_device(nullptr),
- m_id(0)
+emu_timer::emu_timer()
{
+ // nothing is initialized by default because we explicitly
+ // do that in the init_* calls
}
@@ -72,77 +66,99 @@ emu_timer::~emu_timer()
//-------------------------------------------------
-// init - completely initialize the state when
-// re-allocated as a non-device timer
+// init_persistent - initialize a persistent
+// system or device timer; persistent timers can
+// be saved and start off in a disabled state
//-------------------------------------------------
-inline emu_timer &emu_timer::init(running_machine &machine, timer_expired_delegate callback, void *ptr, bool temporary)
+inline emu_timer &emu_timer::init_persistent(running_machine &machine, timer_expired_delegate callback, void *ptr)
{
// ensure the entire timer state is clean
m_machine = &machine;
m_next = nullptr;
m_prev = nullptr;
- m_callback = callback;
- m_param = 0;
- m_ptr = ptr;
- m_enabled = false;
- m_temporary = temporary;
- m_period = attotime::never;
m_start = machine.time();
m_expire = attotime::never;
+ m_period = attotime::never;
+ m_ptr = ptr;
+ m_param = 0;
+ m_enabled = false;
+ m_temporary = false;
+ m_callback = callback.isnull() ? s_dummy_delegate : callback;
+ m_device = nullptr;
m_id = 0;
- // if we're not temporary, register ourselves with the save state system
- if (!m_temporary)
- register_save();
-
- // insert into the list
- machine.scheduler().timer_list_insert(*this);
+ // register ourselves with the save state system
+ register_save();
return *this;
}
-
-//-------------------------------------------------
-// init - completely initialize the state when
-// re-allocated as a device timer
-//-------------------------------------------------
-
-inline emu_timer &emu_timer::init(device_t &device, device_timer_id id, void *ptr, bool temporary)
+inline emu_timer &emu_timer::init_persistent(device_t &device, device_timer_id id, void *ptr)
{
// ensure the entire timer state is clean
m_machine = &device.machine();
m_next = nullptr;
m_prev = nullptr;
- m_callback = timer_expired_delegate(FUNC(emu_timer::device_timer_expired), this);
- m_param = 0;
+ m_start = m_machine->time();
+ m_expire = attotime::never;
+ m_period = attotime::never;
m_ptr = ptr;
+ m_param = 0;
m_enabled = false;
- m_temporary = temporary;
- m_period = attotime::never;
- m_start = machine().time();
- m_expire = attotime::never;
+ m_temporary = false;
+ m_callback = timer_expired_delegate(FUNC(emu_timer::device_timer_expired), this);
m_device = &device;
m_id = id;
- // if we're not temporary, register ourselves with the save state system
- if (!m_temporary)
- register_save();
-
- // insert into the list
- machine().scheduler().timer_list_insert(*this);
+ // register ourselves with the save state system
+ register_save();
return *this;
}
//-------------------------------------------------
-// release - release us from the global list
-// management when deallocating
+// init_temporary - initialize a temporary
+// system timer; temporary timers have a parameter
+// and expiration time from the outset
//-------------------------------------------------
-inline emu_timer &emu_timer::release()
+inline emu_timer &emu_timer::init_temporary(running_machine &machine, timer_expired_delegate callback, void *ptr, int param, attotime const &duration)
+{
+ // ensure the entire timer state is clean
+ m_machine = &machine;
+ m_next = nullptr;
+ m_prev = nullptr;
+ m_start = machine.time();
+ m_expire = m_start + duration;
+ m_period = attotime::never;
+ m_ptr = ptr;
+ m_param = param;
+ m_enabled = true;
+ m_temporary = true;
+ m_callback = callback.isnull() ? s_dummy_delegate : callback;
+ m_device = nullptr;
+ m_id = 0;
+
+ return *this;
+}
+
+inline emu_timer &emu_timer::init_temporary(device_t &device, device_timer_id id, void *ptr, int param, attotime const &duration)
{
- // unhook us from the global list
- machine().scheduler().timer_list_remove(*this);
+ // ensure the entire timer state is clean
+ m_machine = &device.machine();
+ m_next = nullptr;
+ m_prev = nullptr;
+ m_start = m_machine->time();
+ m_expire = m_start + duration;
+ m_period = attotime::never;
+ m_ptr = ptr;
+ m_param = param;
+ m_enabled = true;
+ m_temporary = true;
+ m_callback = timer_expired_delegate(FUNC(emu_timer::device_timer_expired), this);
+ m_device = &device;
+ m_id = id;
+
return *this;
}
@@ -156,14 +172,7 @@ bool emu_timer::enable(bool enable)
// reschedule only if the state has changed
const bool old = m_enabled;
if (old != enable)
- {
- // set the enable flag
- m_enabled = enable;
-
- // remove the timer and insert back into the list
- machine().scheduler().timer_list_remove(*this);
- machine().scheduler().timer_list_insert(*this);
- }
+ machine().scheduler().timer_list_move(*this, m_expire, enable);
return old;
}
@@ -176,31 +185,18 @@ bool emu_timer::enable(bool enable)
void emu_timer::adjust(attotime start_delay, s32 param, const attotime &period)
{
- // if this is the callback timer, mark it modified
- device_scheduler &scheduler = machine().scheduler();
- if (scheduler.m_callback_timer == this)
- scheduler.m_callback_timer_modified = true;
-
- // compute the time of the next firing and insert into the list
- m_param = param;
- m_enabled = true;
-
// clamp negative times to 0
if (start_delay.seconds() < 0)
start_delay = attotime::zero;
// set the start and expire times
+ device_scheduler &scheduler = machine().scheduler();
m_start = scheduler.time();
- m_expire = m_start + start_delay;
- m_period = period;
-
- // remove and re-insert the timer in its new order
- scheduler.timer_list_remove(*this);
- scheduler.timer_list_insert(*this);
+ m_period = period.is_zero() ? attotime::never : period;
+ m_param = param;
- // if this was inserted as the head, abort the current timeslice and resync
- if (this == scheduler.first_timer())
- scheduler.abort_timeslice();
+ // move it into place
+ scheduler.timer_list_move(*this, m_start + start_delay, true);
}
@@ -244,7 +240,16 @@ void emu_timer::register_save()
{
// for non-device timers, it is an index based on the callback function name
name = m_callback.name() ? m_callback.name() : "unnamed";
- for (emu_timer *curtimer = machine().scheduler().first_timer(); curtimer != nullptr; curtimer = curtimer->next())
+ for (emu_timer *curtimer = machine().scheduler().m_active_timers.head(); curtimer != nullptr; curtimer = curtimer->next())
+ if (!curtimer->m_temporary && curtimer->m_device == nullptr)
+ {
+ if (curtimer->m_callback.name() != nullptr && m_callback.name() != nullptr && strcmp(curtimer->m_callback.name(), m_callback.name()) == 0)
+ index++;
+ else if (curtimer->m_callback.name() == nullptr && m_callback.name() == nullptr)
+ index++;
+ }
+
+ for (emu_timer *curtimer = machine().scheduler().m_inactive_timers.head(); curtimer != nullptr; curtimer = curtimer->next())
if (!curtimer->m_temporary && curtimer->m_device == nullptr)
{
if (curtimer->m_callback.name() != nullptr && m_callback.name() != nullptr && strcmp(curtimer->m_callback.name(), m_callback.name()) == 0)
@@ -257,7 +262,11 @@ void emu_timer::register_save()
{
// for device timers, it is an index based on the device and timer ID
name = string_format("%s/%d", m_device->tag(), m_id);
- for (emu_timer *curtimer = machine().scheduler().first_timer(); curtimer != nullptr; curtimer = curtimer->next())
+ for (emu_timer *curtimer = machine().scheduler().m_active_timers.head(); curtimer != nullptr; curtimer = curtimer->next())
+ if (!curtimer->m_temporary && curtimer->m_device == m_device && curtimer->m_id == m_id)
+ index++;
+
+ for (emu_timer *curtimer = machine().scheduler().m_inactive_timers.head(); curtimer != nullptr; curtimer = curtimer->next())
if (!curtimer->m_temporary && curtimer->m_device == m_device && curtimer->m_id == m_id)
index++;
}
@@ -272,24 +281,6 @@ void emu_timer::register_save()
//-------------------------------------------------
-// schedule_next_period - schedule the next
-// period
-//-------------------------------------------------
-
-inline void emu_timer::schedule_next_period()
-{
- // advance by one period
- m_start = m_expire;
- m_expire += m_period;
-
- // remove and re-insert us
- device_scheduler &scheduler = machine().scheduler();
- scheduler.timer_list_remove(*this);
- scheduler.timer_list_insert(*this);
-}
-
-
-//-------------------------------------------------
// dump - dump internal state to a single output
// line in the error log
//-------------------------------------------------
@@ -320,6 +311,289 @@ void emu_timer::device_timer_expired(emu_timer &timer, void *ptr, s32 param)
//**************************************************************************
+// BASETIME-RELATIVE
+//**************************************************************************
+
+//-------------------------------------------------
+// basetime_relative - constructor
+//-------------------------------------------------
+
+device_scheduler::basetime_relative::basetime_relative() :
+ m_relative(0),
+ m_absolute(attotime::zero),
+ m_absolute_dirty(false),
+ m_base_seconds(0)
+{
+}
+
+
+//-------------------------------------------------
+// set - set an absolute time, updating the
+// base-relative time as well
+//-------------------------------------------------
+
+void device_scheduler::basetime_relative::set(attotime const &src)
+{
+ m_absolute = src;
+ m_absolute_dirty = false;
+ update_relative();
+}
+
+
+//-------------------------------------------------
+// add - add attoseconds to the base-relative
+// time, marking the absolute time dirty for
+// later conversion if needed
+//-------------------------------------------------
+
+void device_scheduler::basetime_relative::add(attoseconds_t src)
+{
+ m_relative += src;
+ m_absolute_dirty = true;
+}
+
+
+//-------------------------------------------------
+// set_base_seconds - set the base seconds value
+//-------------------------------------------------
+
+void device_scheduler::basetime_relative::set_base_seconds(seconds_t base)
+{
+ // update the absolute time if dirty first
+ if (m_absolute_dirty)
+ update_absolute();
+
+ // then set and recompute the relative from the absolute time
+ m_base_seconds = base;
+ update_relative();
+}
+
+
+//-------------------------------------------------
+// update_relative - update the relative time
+// from the absolute time
+//-------------------------------------------------
+
+void device_scheduler::basetime_relative::update_relative()
+{
+ seconds_t delta = m_absolute.seconds() - m_base_seconds;
+
+ // if the seconds match, then the relative time is fine as-is
+ m_relative = m_absolute.attoseconds();
+ if (delta == 0)
+ return;
+
+ // if the absolute time is ahead/behind, we need to add/subtract
+ // ATTOSECONDS_PER_SECOND; but only do it once
+ if (delta > 0)
+ {
+ if (delta == 1)
+ m_relative += ATTOSECONDS_PER_SECOND;
+ else
+ m_relative = MAX_RELATIVE;
+ }
+ else
+ {
+ if (delta == -1)
+ m_relative -= ATTOSECONDS_PER_SECOND;
+ else
+ m_relative = MIN_RELATIVE;
+ }
+}
+
+
+//-------------------------------------------------
+// update_absolute - update the absolute time
+// from the relative time
+//-------------------------------------------------
+
+void device_scheduler::basetime_relative::update_absolute()
+{
+ seconds_t secs = m_base_seconds;
+ attoseconds_t attos = m_relative;
+
+ // if relative is outside of range, adjust the seconds
+ if (attos >= ATTOSECONDS_PER_SECOND)
+ {
+ attos -= ATTOSECONDS_PER_SECOND;
+ secs++;
+ }
+ else if (attos < 0)
+ {
+ attos += ATTOSECONDS_PER_SECOND;
+ secs--;
+ }
+
+ // set the new value and clear any dirtiness
+ m_absolute.set_seconds(secs);
+ m_absolute.set_attoseconds(attos);
+ m_absolute_dirty = false;
+}
+
+
+
+//**************************************************************************
+// TIMER LIST
+//**************************************************************************
+
+//-------------------------------------------------
+// timer_list - constructor
+//-------------------------------------------------
+
+device_scheduler::timer_list::timer_list() :
+ m_head(nullptr),
+ m_tail(nullptr)
+{
+}
+
+
+//-------------------------------------------------
+// ~timer_list - destructor
+//-------------------------------------------------
+
+device_scheduler::timer_list::~timer_list()
+{
+ // we own the timers, so delete them when we go away
+ while (m_head != nullptr)
+ delete remove_head();
+}
+
+
+//-------------------------------------------------
+// insert_sorted - insert a timer into the list,
+// sorted by expiration time
+//-------------------------------------------------
+
+bool device_scheduler::timer_list::insert_sorted(emu_timer &timer)
+{
+ // special case insert at start
+ if (m_head == nullptr || timer.m_expire < m_head->m_expire)
+ {
+ insert_head(timer);
+ return true;
+ }
+
+ // special case insert at end
+ if (timer.m_expire >= m_tail->m_expire)
+ {
+ insert_tail(timer);
+ return false;
+ }
+
+ // scan to find out where we go
+ for (emu_timer *scan = m_head->m_next; scan != nullptr; scan = scan->m_next)
+ if (timer.m_expire < scan->m_expire)
+ {
+ timer.m_prev = scan->m_prev;
+ timer.m_next = scan;
+ scan->m_prev->m_next = &timer;
+ scan->m_prev = &timer;
+ return false;
+ }
+
+ // should never get here
+ assert(false);
+ return false;
+}
+
+
+//-------------------------------------------------
+// insert_head - insert a timer at the head of
+// the list
+//-------------------------------------------------
+
+emu_timer &device_scheduler::timer_list::insert_head(emu_timer &timer)
+{
+ // no previous, next is the head
+ timer.m_prev = nullptr;
+ timer.m_next = m_head;
+
+ // if we had a head, link us as the previous and make us head;
+ // otherwise, the list was empty, so we're head and tail
+ if (m_head != nullptr)
+ {
+ m_head->m_prev = &timer;
+ m_head = &timer;
+ }
+ else
+ m_head = m_tail = &timer;
+ return timer;
+}
+
+
+//-------------------------------------------------
+// insert_tail - insert a timer at the tail of
+// the list
+//-------------------------------------------------
+
+emu_timer &device_scheduler::timer_list::insert_tail(emu_timer &timer)
+{
+ // no next, previous is the tail
+ timer.m_prev = m_tail;
+ timer.m_next = nullptr;
+
+ // if we had a tail, link us as the next and make us tail;
+ // otherwise, the list was empty, so we're head and tail
+ if (m_tail != nullptr)
+ {
+ m_tail->m_next = &timer;
+ m_tail = &timer;
+ }
+ else
+ m_tail = m_head = &timer;
+ return timer;
+}
+
+
+//-------------------------------------------------
+// remove - remove a timer from the list
+//-------------------------------------------------
+
+emu_timer &device_scheduler::timer_list::remove(emu_timer &timer)
+{
+ // link the previous to us; if no previous, we're the head
+ if (timer.m_prev != nullptr)
+ timer.m_prev->m_next = timer.m_next;
+ else
+ m_head = timer.m_next;
+
+ // link the next to us; if no next, we're the tail
+ if (timer.m_next != nullptr)
+ timer.m_next->m_prev = timer.m_prev;
+ else
+ m_tail = timer.m_prev;
+
+ // return the timer back for chaining
+ return timer;
+}
+
+
+//-------------------------------------------------
+// remove_head - remove the head item from the
+// list, returning it or nullptr if the list was
+// empty
+//-------------------------------------------------
+
+emu_timer *device_scheduler::timer_list::remove_head()
+{
+ // no head, empty result
+ if (m_head == nullptr)
+ return nullptr;
+
+ // advance the head and fix up the previous pointer
+ emu_timer *result = m_head;
+ m_head = m_head->m_next;
+ if (m_head != nullptr)
+ m_head->m_prev = nullptr;
+ else
+ m_tail = nullptr;
+
+ return result;
+}
+
+
+
+//**************************************************************************
// DEVICE SCHEDULER
//**************************************************************************
@@ -332,17 +606,13 @@ device_scheduler::device_scheduler(running_machine &machine) :
m_executing_device(nullptr),
m_execute_list(nullptr),
m_basetime(attotime::zero),
- m_timer_list(nullptr),
+ m_free_timers(nullptr),
m_callback_timer(nullptr),
m_callback_timer_modified(false),
m_callback_timer_expire_time(attotime::zero),
m_suspend_changes_pending(true),
m_quantum_minimum(ATTOSECONDS_IN_NSEC(1) / 1000)
{
- // append a single never-expiring timer so there is always one in the list
- m_timer_list = &m_timer_allocator.alloc()->init(machine, timer_expired_delegate(), nullptr, true);
- m_timer_list->adjust(attotime::never);
-
// register global states
machine.save().save_item(NAME(m_basetime));
machine.save().register_presave(save_prepost_delegate(FUNC(device_scheduler::presave), this));
@@ -356,9 +626,12 @@ device_scheduler::device_scheduler(running_machine &machine) :
device_scheduler::~device_scheduler()
{
- // remove all timers
- while (m_timer_list != nullptr)
- m_timer_allocator.reclaim(m_timer_list->release());
+ emu_timer *head;
+ while ((head = m_free_timers) != nullptr)
+ {
+ m_free_timers = head->m_next;
+ delete head;
+ }
}
@@ -385,8 +658,9 @@ attotime device_scheduler::time() const noexcept
bool device_scheduler::can_save() const
{
- // if any live temporary timers exit, fail
- for (emu_timer *timer = m_timer_list; timer != nullptr; timer = timer->next())
+ // if any live temporary timers exist, fail (temporary timers are
+ // always active, so only need to scan the active list)
+ for (emu_timer *timer = m_active_timers.head(); timer != nullptr; timer = timer->next())
if (timer->m_temporary && !timer->expire().is_never())
{
machine().logerror("Failed save state attempt due to anonymous timers:\n");
@@ -545,7 +819,7 @@ void device_scheduler::timeslice()
attoseconds_t deltatime = attoseconds_per_cycle * ran;
exec->m_localtime.add(deltatime);
- LOG(" %d ran, %d total, time = %s\n", ran, s32(exec->m_totalcycles), exec->m_localtime.as_string(PRECISION));
+ LOG(" %d ran, %d total, time = %s\n", ran, s32(exec->m_totalcycles), exec->m_localtime.absolute().as_string(PRECISION));
// if the new local device time is less than our target, move the
// target up, but not before the base
@@ -603,12 +877,14 @@ template void device_scheduler::timeslice<false>();
void device_scheduler::update_basetime()
{
+ seconds_t base_seconds = m_basetime.seconds();
+
// update execute devices
for (device_execute_interface &exec : execute_interface_enumerator(machine().root_device()))
- exec.m_localtime.set_base_seconds(m_basetime.seconds());
+ exec.m_localtime.set_base_seconds(base_seconds);
- // update timers
- m_first_timer_expire.set_base_seconds(m_basetime.seconds());
+ // move timers from future list into current list
+ m_first_timer_expire.set_base_seconds(base_seconds);
}
@@ -660,13 +936,45 @@ void device_scheduler::boost_interleave(const attotime &timeslice_time, const at
//-------------------------------------------------
+// timer_alloc_object - allocate memory for a new
+// timer, either by reclaiming a freed one, or
+// allocating memory for a new one
+//-------------------------------------------------
+
+emu_timer &device_scheduler::timer_alloc_object()
+{
+ emu_timer *result = m_free_timers;
+ if (result != nullptr)
+ {
+ m_free_timers = result->m_next;
+ return *result;
+ }
+ return *new emu_timer;
+}
+
+
+//-------------------------------------------------
+// timer_reclaim_object - reclaim memory for a
+// timer by adding it to the free list
+//-------------------------------------------------
+
+void device_scheduler::timer_reclaim_object(emu_timer &timer)
+{
+ timer.m_next = m_free_timers;
+ m_free_timers = &timer;
+}
+
+
+//-------------------------------------------------
// timer_alloc - allocate a global non-device
// timer and return a pointer
//-------------------------------------------------
emu_timer *device_scheduler::timer_alloc(timer_expired_delegate callback, void *ptr)
{
- return &m_timer_allocator.alloc()->init(machine(), callback, ptr, false);
+ // persistent timers are implicitly inactive, so they go on the
+ // inactive list once created
+ return &m_inactive_timers.insert_tail(timer_alloc_object().init_persistent(machine(), callback, ptr));
}
@@ -678,7 +986,14 @@ emu_timer *device_scheduler::timer_alloc(timer_expired_delegate callback, void *
void device_scheduler::timer_set(const attotime &duration, timer_expired_delegate callback, int param, void *ptr)
{
- m_timer_allocator.alloc()->init(machine(), callback, ptr, true).adjust(duration, param);
+ // temporary timers are implicitly active, so add them directly
+ // to the active list after creation
+ emu_timer &timer = timer_alloc_object().init_temporary(machine(), callback, ptr, param, duration);
+ if (m_active_timers.insert_sorted(timer))
+ {
+ update_first_timer_expire();
+ abort_timeslice();
+ }
}
@@ -689,7 +1004,9 @@ void device_scheduler::timer_set(const attotime &duration, timer_expired_delegat
emu_timer *device_scheduler::timer_alloc(device_t &device, device_timer_id id, void *ptr)
{
- return &m_timer_allocator.alloc()->init(device, id, ptr, false);
+ // persistent timers are implicitly inactive, so they go on the
+ // inactive list once created
+ return &m_inactive_timers.insert_tail(timer_alloc_object().init_persistent(device, id, ptr));
}
@@ -701,7 +1018,14 @@ emu_timer *device_scheduler::timer_alloc(device_t &device, device_timer_id id, v
void device_scheduler::timer_set(const attotime &duration, device_t &device, device_timer_id id, int param, void *ptr)
{
- m_timer_allocator.alloc()->init(device, id, ptr, true).adjust(duration, param);
+ // temporary timers are implicitly active, so add them directly
+ // to the active list after creation
+ emu_timer &timer = timer_alloc_object().init_temporary(device, id, ptr, param, duration);
+ if (m_active_timers.insert_sorted(timer))
+ {
+ update_first_timer_expire();
+ abort_timeslice();
+ }
}
@@ -748,26 +1072,42 @@ void device_scheduler::presave()
void device_scheduler::postload()
{
- // remove all timers and make a private list of permanent ones
- simple_list<emu_timer> private_list;
- while (m_timer_list != nullptr)
- {
- emu_timer &timer = *m_timer_list;
+ // upon reloading, all the timers' parameters and expiration times
+ // will be different; because we rely on this information to tell us
+ // which category (active/inactive) they belong to, we have to be
+ // careful in how we maniuplate the timers
- // temporary timers go away entirely (except our special never-expiring one)
- if (timer.m_temporary && !timer.expire().is_never())
- m_timer_allocator.reclaim(timer.release());
+ // our approach here is to remove all the timers in each list directly,
+ // discarding any temporary timers, and appending persistent ones to
+ // a local list; once all timers have been rescued this way, we
+ // reassemble the final list
- // permanent ones get added to our private list
+ timer_list private_list;
+ emu_timer *timer;
+
+ // first discard or capture active timers
+ while ((timer = m_active_timers.remove_head()) != nullptr)
+ if (timer->m_temporary)
+ timer_reclaim_object(*timer);
else
- private_list.append(timer_list_remove(timer));
- }
+ private_list.insert_tail(*timer);
+
+ // then discard or capture inactive timers
+ while ((timer = m_inactive_timers.remove_head()) != nullptr)
+ if (timer->m_temporary)
+ timer_reclaim_object(*timer);
+ else
+ private_list.insert_tail(*timer);
// now re-insert them; this effectively re-sorts them by time
- emu_timer *timer;
- while ((timer = private_list.detach_head()) != nullptr)
- timer_list_insert(*timer);
+ while ((timer = private_list.remove_head()) != nullptr)
+ if (timer->active())
+ m_active_timers.insert_sorted(*timer);
+ else
+ m_inactive_timers.insert_tail(*timer);
+ // force a refresh of things that are lazily updated
+ update_first_timer_expire();
m_suspend_changes_pending = true;
rebuild_execute_list();
@@ -876,74 +1216,66 @@ void device_scheduler::rebuild_execute_list()
//-------------------------------------------------
-// timer_list_insert - insert a new timer into
-// the list at the appropriate location
+// timer_list_move - move an existing timer into
+// the appropriate list at the appropriate
+// location
//-------------------------------------------------
-inline emu_timer &device_scheduler::timer_list_insert(emu_timer &timer)
+inline void device_scheduler::timer_list_move(emu_timer &timer, attotime const &new_expire, bool new_enable)
{
- // disabled timers sort to the end
- const attotime expire = timer.m_enabled ? timer.m_expire : attotime::never;
+ // track the before/after active state to see if it changed
+ bool was_active = timer.active();
+ timer.m_expire = new_expire;
+ timer.m_enabled = new_enable;
- // loop over the timer list
- emu_timer *prevtimer = nullptr;
- for (emu_timer *curtimer = m_timer_list; curtimer != nullptr; prevtimer = curtimer, curtimer = curtimer->next())
+ // if this is the active timer, don't move anything, just mark it modified
+ if (m_callback_timer == &timer)
{
- // if the current list entry expires after us, we should be inserted before it
- if (curtimer->m_expire > expire)
- {
- // link the new guy in before the current list entry
- timer.m_prev = prevtimer;
- timer.m_next = curtimer;
+ m_callback_timer_modified = true;
+ return;
+ }
- if (prevtimer != nullptr)
- prevtimer->m_next = &timer;
- else
- {
- m_timer_list = &timer;
- m_first_timer_expire.set(timer.m_expire);
- }
+ // most common case is becoming/remaining active
+ if (timer.active())
+ {
+ // ok, we're active now; remove us from the previous list
+ (was_active ? m_active_timers : m_inactive_timers).remove(timer);
- curtimer->m_prev = &timer;
- return timer;
+ // if we were inserted at the head, update the first timer expiration; also
+ // abort the current timeslice since the first expire time was moved up
+ if (m_active_timers.insert_sorted(timer))
+ {
+ update_first_timer_expire();
+ abort_timeslice();
}
}
-
- // need to insert after the last one
- if (prevtimer != nullptr)
- prevtimer->m_next = &timer;
- else
+ else if (was_active)
{
- m_timer_list = &timer;
- m_first_timer_expire.set(timer.m_expire);
+ bool was_first = (timer.m_prev == nullptr);
+ m_inactive_timers.insert_tail(m_active_timers.remove(timer));
+
+ // if we were previously the head, update the first timer expiration;
+ // no need to abort the current timeslice because the new expiration
+ // time will be later than before
+ if (was_first)
+ update_first_timer_expire();
}
-
- timer.m_prev = prevtimer;
- timer.m_next = nullptr;
- return timer;
}
//-------------------------------------------------
-// timer_list_remove - remove a timer from the
-// linked list
+// update_first_timer_expire - updated the
+// m_first_timer_expire field based on the head
+// of the active timers list
//-------------------------------------------------
-inline emu_timer &device_scheduler::timer_list_remove(emu_timer &timer)
+void device_scheduler::update_first_timer_expire()
{
- // remove it from the list
- if (timer.m_prev != nullptr)
- timer.m_prev->m_next = timer.m_next;
+ emu_timer *timer = m_active_timers.head();
+ if (timer != nullptr)
+ m_first_timer_expire.set(timer->m_expire);
else
- {
- m_timer_list = timer.m_next;
- m_first_timer_expire.set((timer.m_next != nullptr) ? timer.m_next->m_expire : attotime::never);
- }
-
- if (timer.m_next != nullptr)
- timer.m_next->m_prev = timer.m_prev;
-
- return timer;
+ m_first_timer_expire.set(attotime(m_basetime.seconds() + 1, m_basetime.attoseconds()));
}
@@ -953,28 +1285,23 @@ inline emu_timer &device_scheduler::timer_list_remove(emu_timer &timer)
inline void device_scheduler::execute_timers()
{
- LOG("execute_timers: new=%s head->expire=%s\n", m_basetime.as_string(PRECISION), m_timer_list->m_expire.as_string(PRECISION));
+ LOG("execute_timers: new=%s head->expire=%s\n", m_basetime.as_string(PRECISION), m_first_timer_expire.absolute().as_string(PRECISION));
// now process any timers that are overdue
- while (m_timer_list->m_expire <= m_basetime)
+ while (m_active_timers.head() != nullptr && m_active_timers.head()->m_expire <= m_basetime)
{
// if this is a one-shot timer, disable it now
- emu_timer &timer = *m_timer_list;
- bool was_enabled = timer.m_enabled;
- if (timer.m_period.is_zero() || timer.m_period.is_never())
- timer.m_enabled = false;
-
- // set the global state of which callback we're in
- m_callback_timer_modified = false;
- m_callback_timer = &timer;
- m_callback_timer_expire_time = timer.m_expire;
-
- // call the callback
- if (was_enabled)
+ emu_timer &timer = *m_active_timers.remove_head();
+
+ // handle temporary timers specially
+ if (timer.m_temporary)
{
- g_profiler.start(PROFILER_TIMER_CALLBACK);
+ // set the global state of which callback we're in
+ m_callback_timer = &timer;
+ m_callback_timer_expire_time = timer.m_expire;
- if (!timer.m_callback.isnull())
+ // call the callback
+ g_profiler.start(PROFILER_TIMER_CALLBACK);
{
if (timer.m_device != nullptr)
LOG("execute_timers: timer device %s timer %d\n", timer.m_device->tag(), timer.m_id);
@@ -982,23 +1309,51 @@ inline void device_scheduler::execute_timers()
LOG("execute_timers: timer callback %s\n", timer.m_callback.name());
timer.m_callback(timer.m_ptr, timer.m_param);
}
-
g_profiler.stop();
- }
- // reset or remove the timer, but only if it wasn't modified during the callback
- if (!m_callback_timer_modified)
+ // reclaim the timer now that we're done with it
+ timer_reclaim_object(timer);
+ }
+ else
{
- // if the timer is temporary, remove it now
- if (timer.m_temporary)
- m_timer_allocator.reclaim(timer.release());
+ // if not repeating, mark the timer disabled now
+ if (timer.m_period.is_never())
+ timer.m_enabled = false;
- // otherwise, reschedule it
+ // set the global state of which callback we're in
+ m_callback_timer_modified = false;
+ m_callback_timer = &timer;
+ m_callback_timer_expire_time = timer.m_expire;
+
+ // call the callback
+ g_profiler.start(PROFILER_TIMER_CALLBACK);
+ {
+ if (timer.m_device != nullptr)
+ LOG("execute_timers: timer device %s timer %d\n", timer.m_device->tag(), timer.m_id);
+ else
+ LOG("execute_timers: timer callback %s\n", timer.m_callback.name());
+ timer.m_callback(timer.m_ptr, timer.m_param);
+ }
+ g_profiler.stop();
+
+ // if the timer wasn't modified during the callback, advance by one period
+ if (!m_callback_timer_modified)
+ {
+ timer.m_start = timer.m_expire;
+ timer.m_expire += timer.m_period;
+ }
+
+ // insert into the appropriate list
+ if (timer.active())
+ m_active_timers.insert_sorted(timer);
else
- timer.schedule_next_period();
+ m_inactive_timers.insert_tail(timer);
}
}
+ // update the expiration time of the first timer
+ update_first_timer_expire();
+
// clear the callback timer global
m_callback_timer = nullptr;
}
@@ -1057,7 +1412,9 @@ void device_scheduler::dump_timers() const
{
machine().logerror("=============================================\n");
machine().logerror("Timer Dump: Time = %15s\n", time().as_string(PRECISION));
- for (emu_timer *timer = first_timer(); timer != nullptr; timer = timer->next())
+ for (emu_timer *timer = m_active_timers.head(); timer != nullptr; timer = timer->next())
+ timer->dump();
+ for (emu_timer *timer = m_inactive_timers.head(); timer != nullptr; timer = timer->next())
timer->dump();
machine().logerror("=============================================\n");
}
diff --git a/src/emu/schedule.h b/src/emu/schedule.h
index 9581b4cd337..0cf72c1a072 100644
--- a/src/emu/schedule.h
+++ b/src/emu/schedule.h
@@ -29,108 +29,6 @@
// TYPE DEFINITIONS
//**************************************************************************
-class basetime_relative
-{
-public:
- static constexpr attoseconds_t MAX_RELATIVE = 2 * ATTOSECONDS_PER_SECOND;
- static constexpr attoseconds_t MIN_RELATIVE = -MAX_RELATIVE;
-
- basetime_relative() :
- m_relative(0),
- m_absolute(attotime::zero),
- m_absolute_dirty(false),
- m_base_seconds(0)
- {
- }
-
- void set(attotime const &src)
- {
- m_absolute = src;
- m_absolute_dirty = false;
- update_relative();
- }
-
- void add(attoseconds_t src)
- {
- m_relative += src;
- m_absolute_dirty = true;
- }
-
- void set_base_seconds(seconds_t base)
- {
- if (base != m_base_seconds)
- {
- if (m_absolute_dirty)
- update_absolute();
- m_base_seconds = base;
- update_relative();
- }
- }
-
- attoseconds_t relative() const { return m_relative; }
- attotime const &absolute()
- {
- if (m_absolute_dirty)
- update_absolute();
- return m_absolute;
- }
-
- const char *as_string(int precision = 9)
- {
- if (m_absolute_dirty)
- update_absolute();
- return m_absolute.as_string(precision);
- }
-
-private:
- void update_relative()
- {
- seconds_t delta = m_absolute.seconds() - m_base_seconds;
- m_relative = m_absolute.attoseconds();
- if (delta == 0)
- return;
- if (delta > 0)
- {
- if (delta == 1)
- m_relative += ATTOSECONDS_PER_SECOND;
- else
- m_relative = MAX_RELATIVE;
- }
- else
- {
- if (delta == -1)
- m_relative -= ATTOSECONDS_PER_SECOND;
- else
- m_relative = MIN_RELATIVE;
- }
- }
-
- void update_absolute()
- {
- seconds_t secs = m_base_seconds;
- attoseconds_t attos = m_relative;
- if (attos >= ATTOSECONDS_PER_SECOND)
- {
- attos -= ATTOSECONDS_PER_SECOND;
- secs++;
- }
- else if (attos < 0)
- {
- attos += ATTOSECONDS_PER_SECOND;
- secs--;
- }
- m_absolute.set_seconds(secs);
- m_absolute.set_attoseconds(attos);
- m_absolute_dirty = false;
- }
-
- attoseconds_t m_relative;
- attotime m_absolute;
- bool m_absolute_dirty;
- seconds_t m_base_seconds;
-};
-
-
// timer callbacks look like this
typedef named_delegate<void (void *, s32)> timer_expired_delegate;
@@ -139,24 +37,25 @@ typedef named_delegate<void (void *, s32)> timer_expired_delegate;
class emu_timer
{
friend class device_scheduler;
- friend class simple_list<emu_timer>;
- friend class fixed_allocator<emu_timer>;
- friend class resource_pool_object<emu_timer>;
+ friend class timer_list;
// construction/destruction
emu_timer();
~emu_timer();
// allocation and re-use
- emu_timer &init(running_machine &machine, timer_expired_delegate callback, void *ptr, bool temporary);
- emu_timer &init(device_t &device, device_timer_id id, void *ptr, bool temporary);
- emu_timer &release();
+ emu_timer &init_persistent(running_machine &machine, timer_expired_delegate callback, void *ptr);
+ emu_timer &init_persistent(device_t &device, device_timer_id id, void *ptr);
+ emu_timer &init_temporary(running_machine &machine, timer_expired_delegate callback, void *ptr, int param, attotime const &duration);
+ emu_timer &init_temporary(device_t &device, device_timer_id id, void *ptr, int param, attotime const &duration);
public:
// getters
+ emu_timer *prev() const { return m_prev; }
emu_timer *next() const { return m_next; }
running_machine &machine() const noexcept { assert(m_machine != nullptr); return *m_machine; }
bool enabled() const { return m_enabled; }
+ bool active() const { return m_enabled && !m_expire.is_never(); }
int param() const { return m_param; }
void *ptr() const { return m_ptr; }
@@ -179,7 +78,6 @@ public:
private:
// internal helpers
void register_save();
- void schedule_next_period();
void dump() const;
static void device_timer_expired(emu_timer &timer, void *ptr, s32 param);
@@ -187,14 +85,14 @@ private:
running_machine * m_machine; // reference to the owning machine
emu_timer * m_next; // next timer in order in the list
emu_timer * m_prev; // previous timer in order in the list
- timer_expired_delegate m_callback; // callback function
- s32 m_param; // integer parameter
+ attotime m_expire; // time when the timer will expire
+ attotime m_period; // the repeat frequency of the timer
+ attotime m_start; // time when the timer was started
void * m_ptr; // pointer parameter
+ s32 m_param; // integer parameter
bool m_enabled; // is the timer enabled?
bool m_temporary; // is the timer temporary?
- attotime m_period; // the repeat frequency of the timer
- attotime m_start; // time when the timer was started
- attotime m_expire; // time when the timer will expire
+ timer_expired_delegate m_callback; // callback function
device_t * m_device; // for device timers, a pointer to the device
device_timer_id m_id; // for device timers, the ID of the timer
};
@@ -208,6 +106,64 @@ class device_scheduler
friend class basetime_relative;
friend class emu_timer;
+ class basetime_relative
+ {
+ public:
+ // minima/maxima
+ static constexpr attoseconds_t MAX_RELATIVE = 2 * ATTOSECONDS_PER_SECOND;
+ static constexpr attoseconds_t MIN_RELATIVE = -MAX_RELATIVE;
+
+ // construction/destruction
+ basetime_relative();
+
+ // set an absolute time
+ void set(attotime const &src);
+
+ // add a number of attoseconds to the relative time
+ void add(attoseconds_t src);
+
+ // set the base for the relative time
+ void set_base_seconds(seconds_t base);
+
+ // return the relative time
+ attoseconds_t relative() const { return m_relative; }
+
+ // return the absolute time, updating if dirty
+ attotime const &absolute() { if (m_absolute_dirty) update_absolute(); return m_absolute; }
+
+ private:
+ // internal helpers
+ void update_relative();
+ void update_absolute();
+
+ // internal state
+ attoseconds_t m_relative;
+ attotime m_absolute;
+ bool m_absolute_dirty;
+ seconds_t m_base_seconds;
+ };
+
+ class timer_list
+ {
+ public:
+ timer_list();
+ ~timer_list();
+
+ emu_timer *head() const { return m_head; }
+ emu_timer *tail() const { return m_tail; }
+
+ bool insert_sorted(emu_timer &timer);
+ emu_timer &insert_head(emu_timer &timer);
+ emu_timer &insert_tail(emu_timer &timer);
+
+ emu_timer *remove_head();
+ emu_timer &remove(emu_timer &timer);
+
+ private:
+ emu_timer *m_head;
+ emu_timer *m_tail;
+ };
+
public:
// construction/destruction
device_scheduler(running_machine &machine);
@@ -216,7 +172,7 @@ public:
// getters
running_machine &machine() const noexcept { return m_machine; }
attotime time() const noexcept;
- emu_timer *first_timer() const { return m_timer_list; }
+ emu_timer *first_timer();
device_execute_interface *currently_executing() const noexcept { return m_executing_device; }
bool can_save() const;
@@ -255,8 +211,10 @@ private:
void add_scheduling_quantum(const attotime &quantum, const attotime &duration);
// timer helpers
- emu_timer &timer_list_insert(emu_timer &timer);
- emu_timer &timer_list_remove(emu_timer &timer);
+ emu_timer &timer_alloc_object();
+ void timer_reclaim_object(emu_timer &timer);
+ void timer_list_move(emu_timer &timer, attotime const &new_expire, bool new_enable);
+ void update_first_timer_expire();
void execute_timers();
void update_basetime();
@@ -271,8 +229,9 @@ private:
// list of active timers
basetime_relative m_first_timer_expire; // time of the first timer expiration
- emu_timer * m_timer_list; // head of the active list
- fixed_allocator<emu_timer> m_timer_allocator; // allocator for timers
+ timer_list m_active_timers; // sorted list of active timers
+ timer_list m_inactive_timers; // unsorted list of inactive timers
+ emu_timer * m_free_timers; // simple list of free timers
// other internal states
emu_timer * m_callback_timer; // pointer to the current callback timer