Commit 5d64f789 authored by Ben Lambell's avatar Ben Lambell

Refactor events system for simpler handling of memory/references

parent cd710a1a
I have decided to remove the concept of Events being 'pending' or not.
Instead, an event has the following members:
+ Time : IDependency<T>
+ Occur()
+ Register(EventQueue)
We assume the event will never occur again as soon as there are no event handlers (or similar) which will set the Time value.
In this way, the garbage collector can do its job.
The Register method will ensure that, as soon as the event's Time becomes invalidated, it will be stored in memory by the EventQueue.
The EventQueue will then resolve the Time and, if not NULL, hold the Event in memory until it occurs.
There will be no such thing as a CompositeEvent. This is not necessary, as events are pretty basic and have no sensible aggregates.
If we have a set of events which should be cancelled all at once (like Spermarche, Menopause, and other life events), they should just include that logic internally (i.e., for life events, by updating their Time based on whether the person is alive).
Old (possibly obsolete) notes follow:
Consider adding
- EventTime class to encapsulate when an event occurs (Time, Pending, OnOccur)
- Subclasses: OccurAt, OccurAfter
- Events: IEvent (OnOccur(T)), TimedEvent (takes EventTime or Func<EventTime>), CompositeEvent
- Saga events: OccurRandomly, OccurAfterAge, OccurAfterRange, OccurInRange
- Occurence class to encapsulate an event's occurence
- Class LifeEvents(Human) : CompositeEvent can store Spermarche, Menopause, etc... (w methods OnSpermarche, etc...)
- public Ovulation(Human human) : base(new OccurAfter(human.Cosmos.Time.Add(Period.Get(Saga.Random))))
new Event(new Ovulation(this), new RandomEventTime(Cosmos.Time, Ovulation.Period))
public Ovulation(Human human, Time time) : base(new OccurOnceAt(time)) ...as opposed to new OccurOnceBy(time)
{
}
\ No newline at end of file
/*
* Copyright (c) 2017 Ben Lambell.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using Recurse.Dependencies;
using Recurse.Dependencies.Variables.Concrete;
namespace Recurse.Scheduling.Events.Abstract
{
[Serializable]
public abstract class MutableEvent<TTime> : IEvent<TTime>
{
private readonly MutableVariable<TTime> _time = new MutableVariable<TTime>();
private readonly MutableVariable<bool> _isFixed = new MutableVariable<bool>();
public bool IsFixed
{
get { return _isFixed.Value; }
set
{
if (Equals(IsFixed, value)) return;
if (IsFixed)
throw new InvalidOperationException("Cannot change IsFixed from TRUE to FALSE.");
_isFixed.Value = value;
}
}
public TTime Time
{
get { return _time.Value; }
set
{
if (Equals(Time, value)) return;
if (IsFixed)
throw new InvalidOperationException("Event time is fixed and cannot be changed.");
_time.Value = value;
}
}
#region IEvent Implementation
bool IEvent<TTime>.IsFixed => _isFixed.Value;
public IDependency IsFixedDependency => _isFixed;
TTime IEvent<TTime>.Time => _time.Value;
public IDependency TimeDependency => _time;
public abstract void Occur();
#endregion
}
}
/*
* Copyright (c) 2017 Ben Lambell.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using Recurse.Dependencies;
using Recurse.Dependencies.Variables.Abstract;
using Recurse.Dependencies.Variables.Concrete;
namespace Recurse.Scheduling.Events
{
[Serializable]
public class CompositeEvent<TTime> : IEvent<TTime?>
where TTime : struct, IComparable<TTime>
{
#region Backing Fields
private readonly MutableVariable<bool> _isFixed = new MutableVariable<bool>();
private readonly TransformedVariable<IEvent<TTime?>, TTime?> _time;
#endregion
private readonly OrderedEvents _orderedEvents;
private readonly NextEvent _next;
private readonly List<IEvent<TTime?>> _entries = new List<IEvent<TTime?>>();
public bool IsFixed => _isFixed.Value;
public IDependency IsFixedDependency => _isFixed;
public TTime? Time => _time.GetValue(e => e?.TimeDependency, e => e?.Time);
public IDependency TimeDependency => _time;
public IEvent<TTime?> Next => _next.Value;
public CompositeEvent()
{
_orderedEvents = new OrderedEvents(_entries);
_next = new NextEvent(_orderedEvents);
_time = TransformedVariable<TTime?>.Create(_next);
}
public void Add(IEvent<TTime?> e)
{
EnsureNotCancelled();
_entries.Add(e);
_orderedEvents.Invalidate();
e.TimeDependency.Dependants.Add(_orderedEvents);
e.IsFixedDependency.Dependants.Add(_orderedEvents);
}
public void Remove(IEvent<TTime?> e)
{
EnsureNotCancelled();
if (_entries.Remove(e))
{
_next.Invalidate();
e.TimeDependency.Dependants.Remove(_orderedEvents);
e.IsFixedDependency.Dependants.Remove(_orderedEvents);
}
}
public void Cancel()
{
_entries.Clear();
_next.Invalidate();
_isFixed.Value = true;
}
public void Occur()
{
var nextEvent = _next.Value;
if (nextEvent == null)
throw new InvalidOperationException("No event scheduled.");
nextEvent.Occur();
}
private void EnsureNotCancelled()
{
if (IsFixed)
throw new InvalidOperationException("CompositeEvent cancelled.");
}
[Serializable]
private class OrderedEvents : CachedVariable<IList<IEvent<TTime?>>>
{
private readonly List<IEvent<TTime?>> _events;
public OrderedEvents(List<IEvent<TTime?>> events)
{
_events = events;
}
protected override IList<IEvent<TTime?>> GetValidValue()
{
_events.Sort(EventComparer<TTime>.Instance);
var nextEventIndex = _events.FindIndex(e => e.Time.HasValue || !e.IsFixed);
if (nextEventIndex == -1)
_events.Clear();
else
_events.RemoveRange(0, nextEventIndex);
return _events;
}
}
[Serializable]
private class NextEvent : CachedVariable<IEvent<TTime?>>
{
private readonly OrderedEvents _orderedEvents;
public NextEvent(OrderedEvents orderedEvents)
{
_orderedEvents = orderedEvents;
_orderedEvents.Dependants.Add(this);
}
protected override IEvent<TTime?> GetValidValue()
{
var nextEvent = _orderedEvents.Value.FirstOrDefault();
return nextEvent?.Time.HasValue == true ? nextEvent : null;
}
}
}
}
/*
* Copyright (c) 2017 Ben Lambell.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using Recurse.Common.Maths.Specialized;
using Recurse.Dependencies.Variables;
using Recurse.Dependencies.Variables.Abstract;
namespace Recurse.Scheduling.Events
{
[Serializable]
public class DelayedEvent<TTime, TDuration> : DynamicEvent<TTime?>
where TTime : struct, ITime<TTime, TDuration>
where TDuration : struct, IMeasurementUnit
{
public DelayedEvent(
Action occur,
IClock<TTime> timeSource,
IVariable<Measurement<TDuration>?> delay) :
base(occur, new TimeValue(timeSource, delay))
{
}
[Serializable]
private class TimeValue : CachedVariable<TTime?>
{
private readonly IClock<TTime> _timeSource;
private readonly IVariable<Measurement<TDuration>?> _delay;
public TimeValue(
IClock<TTime> timeSource,
IVariable<Measurement<TDuration>?> delay)
{
_delay = delay;
_timeSource = timeSource;
_delay.Dependants.Add(this);
}
protected override TTime? GetValidValue()
{
var delay = _delay.Value;
if (delay.HasValue)
return _timeSource.Time.Add(delay.Value);
return null;
}
}
}
}
......@@ -6,26 +6,24 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using Recurse.Dependencies;
using Recurse.Dependencies.Variables;
namespace Recurse.Scheduling.Events.Abstract
{
[Serializable]
public abstract class Event<TTime> : IEvent<TTime>
public abstract class Event<TTime> : IEvent where TTime : struct
{
IDependency IEvent<TTime>.IsFixedDependency => IsFixedDependency;
IDependency IEvent<TTime>.TimeDependency => TimeDependency;
private readonly Scheduler<TTime> _scheduler;
public bool IsFixed => IsFixedDependency.Value;
public TTime? Time => _scheduler.Time;
public TTime Time => TimeDependency.Value;
protected Event()
{
_scheduler = new Scheduler<TTime>(this);
}
public abstract IVariable<bool> IsFixedDependency { get; }
public void SetTime(ISchedule<TTime> schedule, TTime time)
=> _scheduler.SetTime(schedule, time);
public abstract IVariable<TTime> TimeDependency { get; }
public void ClearTime()
=> _scheduler.ClearTime();
public abstract void Occur();
}
......
using System;
using System.Collections.Generic;
namespace Recurse.Scheduling.Events
{
/// <summary>
/// Compares events such that cancelled events come first (i.e., events with no scheduled
/// time where the scheduled time will never change), followed by scheduled events (from
/// first to last), followed by unscheduled events which may be scheduled in future.
/// </summary>
public class EventComparer<TTime> : IComparer<IEvent<TTime?>>
where TTime : struct, IComparable<TTime>
{
public static EventComparer<TTime> Instance { get; } = new EventComparer<TTime>();
public int Compare(IEvent<TTime?> a, IEvent<TTime?> b)
{
var aSection = GetSection(a);
var bSection = GetSection(b);
var result = aSection.CompareTo(bSection);
if (result != 0 || aSection != 1)
return result;
return a.Time.Value.CompareTo(b.Time.Value);
}
private int GetSection(IEvent<TTime?> e)
{
if (e.IsFixed)
{
if (e.Time.HasValue)
throw new InvalidOperationException("Event scheduled to repeat infinitely");
return 0;
}
return e.Time.HasValue ? 1 : 2;
}
}
}
......@@ -6,31 +6,23 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using Recurse.Dependencies.Variables;
using Recurse.Dependencies.Variables.Concrete;
using Recurse.Scheduling.Events.Abstract;
namespace Recurse.Scheduling.Events
namespace Recurse.Scheduling.Events.Abstract
{
[Serializable]
public class DynamicEvent<TTime> : Event<TTime>
public abstract class TimedEvent<TTime> : IEvent where TTime : struct
{
private readonly Action _occur;
public override IVariable<bool> IsFixedDependency => FixedVariable<bool>.Default;
private readonly TimeScheduler<TTime> _scheduler;
public override IVariable<TTime> TimeDependency { get; }
public DynamicEvent(Action occur, IVariable<TTime> time)
public TTime? Time
{
_occur = occur;
TimeDependency = time;
get { return _scheduler.Time; }
set { _scheduler.Time = value; }
}
public override void Occur()
protected TimedEvent(ISchedule<TTime> schedule)
{
_occur();
_scheduler = new TimeScheduler<TTime>(schedule, this);
}
public abstract void Occur();
}
}
/*
* Copyright (c) 2017 Ben Lambell.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using Recurse.Dependencies;
namespace Recurse.Scheduling.Events
{
[Serializable]
public class WrappedEvent<TTime> : IEvent<TTime>
{
private readonly IEvent<TTime> _wrappedEvent;
public bool IsFixed => _wrappedEvent.IsFixed;
public IDependency IsFixedDependency => _wrappedEvent.IsFixedDependency;
public TTime Time => _wrappedEvent.Time;
public IDependency TimeDependency => _wrappedEvent.TimeDependency;
public WrappedEvent(IEvent<TTime> wrappedEvent)
{
_wrappedEvent = wrappedEvent;
}
public void Occur() => _wrappedEvent.Occur();
}
}
......@@ -6,8 +6,6 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using Recurse.Dependencies;
namespace Recurse.Scheduling
{
/// <summary>
......@@ -20,41 +18,4 @@ namespace Recurse.Scheduling
/// </summary>
void Occur();
}
/// <summary>
/// An event scheduled to occur at a particular time
/// </summary>
public interface IEvent<TTime> : IEvent
{
/// <summary>
/// Indicates the event's time will not change.
/// </summary>
// Note: As long as IsFixed is backed by the dependency
// framework, its constraints cannot be enforced. If something
// invalidates IsFixed, and might make it TRUE, but we cannot
// ensure IsFixed is validated soon enough to set the value. If
// it isn't, it is always possible that its valid value will
// become FALSE again, and we'll have no way to validate it.
bool IsFixed { get; }
/// <summary>
/// <see cref="IDependency"/> backing for
/// <see cref="IsFixed"/>
/// </summary>
IDependency IsFixedDependency { get; }
/// <summary>
/// The time when the event is scheduled to
/// occur. There is no guarantee it will occur
/// at this exact time. NULL values indicate
/// the event is not scheduled to occur.
/// </summary>
TTime Time { get; }
/// <summary>
/// <see cref="IDependency"/> backing for
/// <see cref="Time"/>
/// </summary>
IDependency TimeDependency { get; }
}
}
......@@ -6,14 +6,17 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using Recurse.Common.Maths.Specialized;
using Recurse.Scheduling.Schedules;
namespace Recurse.Scheduling
{
public interface ITime<TTime, TDuration> where TDuration : IMeasurementUnit
public interface ISchedule
{
Measurement<TDuration> GetDifference(TTime other);
bool Remove(IScheduledEvent e);
}
TTime Add(Measurement<TDuration> duration);
public interface ISchedule<TTime> : ISchedule
{
IScheduledEvent<TTime> Schedule(IEvent e, TTime time);
}
}
namespace Recurse.Scheduling.Schedules
{
public interface IScheduledEvent
{
bool Occurred { get; }
}
public interface IScheduledEvent<TTime> : IScheduledEvent
{
TTime Time { get; }
}
}
......@@ -5,11 +5,16 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
namespace Recurse.Scheduling
namespace Recurse.Scheduling.Events.Abstract
{
public class Clock<T> : IClock<T>
public interface IScheduler
{
void ClearTime();
}
public interface IScheduler<TTime> : IScheduler where TTime : struct
{
public T Time { get; set; }
TTime? Time { get; }
}
}
/*
* Copyright (c) 2017 Ben Lambell.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using Recurse.Scheduling.Schedules;
namespace Recurse.Scheduling.Events.Abstract
{
public class Scheduler<TTime> : IScheduler<TTime> where TTime : struct
{
private readonly IEvent _event;
private IScheduledEvent<TTime> _scheduledEvent;
private ISchedule<TTime> _schedule;
public TTime? Time => _scheduledEvent?.Occurred == false ? (TTime?)_scheduledEvent.Time : null;
public Scheduler(IEvent @event)
{
_event = @event;
}
public void SetTime(ISchedule<TTime> schedule, TTime time)
{
ClearTime();
_scheduledEvent = schedule.Schedule(_event, time);
_schedule = schedule;
}
public void ClearTime()
{
_schedule?.Remove(_scheduledEvent);
_scheduledEvent = null;
_schedule = null;
}
}
}
/*
* Copyright (c) 2017 Ben Lambell.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using Recurse.Scheduling.Schedules;
namespace Recurse.Scheduling.Events.Abstract
{
public class TimeScheduler<TTime> : IScheduler where TTime : struct
{
private readonly ISchedule<TTime> _schedule;
private readonly IEvent _event;
private IScheduledEvent<TTime> _scheduledEvent;
public TTime? Time
{
get { return _scheduledEvent?.Occurred == false ? (TTime?)_scheduledEvent.Time : null; }
set
{
if (Equals(Time, value))
return;
_schedule.Remove(_scheduledEvent);
_scheduledEvent = value.HasValue ?
_schedule.Schedule(_event, value.Value) :
null;
}
}
public TimeScheduler(ISchedule<TTime> schedule, IEvent e)
{
_schedule = schedule;
_event = e;
}
public void ClearTime() => Time = null;
}
}
using System;
namespace Recurse.Scheduling.Schedules
{
[Serializable]
public class PartialSchedule<TTime> : ISchedule<TTime> where TTime : IComparable<TTime>
{
private readonly ExecuteNext _executeNext;
private readonly ISchedule<TTime> _parent;
private readonly Schedule<TTime> _schedule = new Schedule<TTime>();
private IScheduledEvent<TTime> _scheduledExecuteNext;
public PartialSchedule(ISchedule<TTime> parent)
{
_parent = parent;
_executeNext = new ExecuteNext(_schedule);
}
public void Clear()
{
_schedule.Clear();
Update();
}
public bool Remove(IScheduledEvent e)
{
var result = _schedule.Remove(e);
Update();
return result;
}
public IScheduledEvent<TTime> Schedule(IEvent e, TTime time)
{
var result = _schedule.Schedule(e, time);
Update();
return result;
}
private void Update()
{
if (_scheduledExecuteNext != null)
{
if (_schedule.Next == null || !Equals(_scheduledExecuteNext.Time, _schedule.Next.Time))
{
_parent.Remove(_scheduledExecuteNext);
_scheduledExecuteNext = null;
}
}
if (_schedule.Next != null && _scheduledExecuteNext == null)
{
_scheduledExecuteNext = _parent.Schedule(_executeNext, _schedule.Next.Time);
}
}
[Serializable]
private class ExecuteNext : IEvent
{
private readonly Schedule<TTime> _schedule;
public ExecuteNext(Schedule<TTime> schedule)
{
_schedule = schedule;
}
public void Occur() => _schedule.ExecuteNext();
}
}
}