1
0
mirror of https://github.com/livingcomputermuseum/Darkstar.git synced 2026-01-12 00:42:59 +00:00
2019-01-15 12:55:18 -08:00

253 lines
8.0 KiB
C#

/*
BSD 2-Clause License
Copyright Vulcan Inc. 2017-2018 and Living Computer Museum + Labs 2018
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
using System.Collections.Generic;
namespace D
{
/// <summary>
/// The SchedulerEventCallback describes a delegate that is invoked whenever a scheduled event has
/// reached its due-date and is fired.
/// </summary>
/// <param name="skew">The delta between the requested exec time and the actual exec time (in nsec)</param>
/// <param name="context">An object containing context useful to the scheduler of the event</param>
public delegate void SchedulerEventCallback(ulong skewNsec, object context);
/// <summary>
/// An Event encapsulates a callback and associated context that is scheduled for a future timestamp.
/// </summary>
public class Event
{
public Event(ulong timestampNsec, object context, SchedulerEventCallback callback)
{
_timestampNsec = timestampNsec;
_context = context;
_callback = callback;
}
/// <summary>
/// The absolute time (in nsec) to raise the event.
/// </summary>
public ulong TimestampNsec
{
get { return _timestampNsec; }
set { _timestampNsec = value; }
}
/// <summary>
/// An object containing context to be passed to the
/// event callback.
/// </summary>
public object Context
{
get { return _context; }
set { _context = value; }
}
/// <summary>
/// A delegate to be executed when the callback fires.
/// </summary>
public SchedulerEventCallback EventCallback
{
get { return _callback; }
}
private ulong _timestampNsec;
private object _context;
private SchedulerEventCallback _callback;
}
/// <summary>
/// The Scheduler class provides infrastructure for scheduling time-based hardware events
/// (for example, sector marks, or video task wakeups).
///
/// Note that the Scheduler is not thread-safe and must only be used from the emulation thread,
/// or else things will break. This is not optimal -- having a thread-safe scheduler would make
/// it easier/cleaner to deal with asynchronous things like ethernet packets and scripting events
/// but doing so incurs about a 10% performance penalty so it's been avoided.
/// </summary>
public class Scheduler
{
public Scheduler()
{
Reset();
}
public ulong CurrentTimeNsec
{
get { return _currentTimeNsec; }
}
public void Reset()
{
_schedule = new SchedulerQueue();
_currentTimeNsec = 0;
}
public void Clock()
{
//
// Move one system clock forward in time.
//
_currentTimeNsec += _timeStepNsec;
//
// See if we have any events waiting to fire at this timestep.
//
while (_schedule.Top != null && _currentTimeNsec >= _schedule.Top.TimestampNsec)
{
// Pop the top event and fire the callback.
Event e = _schedule.Pop();
e.EventCallback(_currentTimeNsec - e.TimestampNsec, e.Context);
}
}
/// <summary>
/// Add a new event to the schedule.
/// </summary>
/// <param name="e"></param>
/// <returns></returns>
public Event Schedule(ulong timestampNsec, object context, SchedulerEventCallback callback)
{
Event e = new Event(timestampNsec + _currentTimeNsec, context, callback);
_schedule.Push(e);
return e;
}
public Event Schedule(ulong timestampNsec, SchedulerEventCallback callback)
{
Event e = new Event(timestampNsec + _currentTimeNsec, null, callback);
_schedule.Push(e);
return e;
}
public void Cancel(Event e)
{
if (e != null)
{
_schedule.Remove(e);
}
}
private ulong _currentTimeNsec;
private SchedulerQueue _schedule;
// 137nsec is approximately one central processor system clock cycle and is the time-base for
// the scheduler.
private const ulong _timeStepNsec = 137;
}
/// <summary>
/// Provides an "ordered" queue based on timestamp -- the top of the queue is always the
/// next event to be fired; a "push" places a new event in order on the current queue.
/// </summary>
public class SchedulerQueue
{
public SchedulerQueue()
{
_queue = new LinkedList<Event>();
}
public Event Top
{
get
{
return _top;
}
}
public bool Contains(Event e)
{
return _queue.Contains(e);
}
public void Push(Event e)
{
// Degenerate case: list is empty or new entry is earlier than the head of the list.
if (_queue.Count == 0 || _top.TimestampNsec >= e.TimestampNsec)
{
_queue.AddFirst(e);
_top = e;
return;
}
//
// Do a linear search to find the place to put this in.
// Since we maintain a sorted list with every insertion we only need to find the first entry
// that the new entry is earlier (or equal) to.
// This will likely be adequate as the queue should never get incredibly deep; a binary
// search may be more performant if this is not the case.
//
LinkedListNode<Event> current = _queue.First;
while (current != null)
{
if (current.Value.TimestampNsec >= e.TimestampNsec)
{
_queue.AddBefore(current, e);
return;
}
current = current.Next;
}
// Add at end
_queue.AddLast(e);
}
public Event Pop()
{
Event e = _top;
_queue.RemoveFirst();
_top = _queue.First != null ? _queue.First.Value : null;
return e;
}
public void Remove(Event e)
{
if (_queue.Contains(e))
{
_queue.Remove(e);
_top = _queue.First.Value;
}
}
private LinkedList<Event> _queue;
/// <summary>
/// The Top of the queue (null if queue is empty).
/// </summary>
private Event _top;
}
}