#region License Information
/* SimSharp - A .NET port of SimPy, discrete event simulation framework
Copyright (C) 2002-2016 Heuristic and Evolutionary Algorithms Laboratory (HEAL)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .*/
#endregion
using System;
using System.Collections.Generic;
namespace SimSharp {
///
/// A Process handles the iteration of events. Processes may define steps that
/// a certain entity in the simulation has to perform. Each time the process
/// should wait it yields an event and will be resumed when that event is processed.
///
///
/// Since an iterator method does not have access to its process, the method can
/// retrieve the associated Process through the ActiveProcess property of the
/// environment. Each Process sets and resets that property during Resume.
///
public class Process : Event {
private readonly IEnumerator generator;
private Event target;
///
/// Target is the event that is expected to be executed next in the process.
///
public Event Target {
get { return target; }
protected set { target = value; }
}
public Process(Environment environment, IEnumerable generator)
: base(environment) {
this.generator = generator.GetEnumerator();
IsOk = true;
target = new Initialize(environment, this);
}
///
/// This interrupts a process and causes the IsOk flag to be set to false.
/// If a process is interrupted the iterator method needs to call HandleFault()
/// before continuing to yield further events.
///
/// This is thrown in three conditions:
/// - If the process has already been triggered.
/// - If the process attempts to interrupt itself.
/// - If the process continues to yield events despite being faulted.
/// The cause of the interrupt.
public virtual void Interrupt(object cause = null) {
if (IsTriggered) throw new InvalidOperationException("The process has terminated and cannot be interrupted.");
if (Environment.ActiveProcess == this) throw new InvalidOperationException("A process is not allowed to interrupt itself.");
var interruptEvent = new Event(Environment);
interruptEvent.AddCallback(Resume);
interruptEvent.Fail(cause);
if (Target != null)
Target.RemoveCallback(Resume);
}
protected virtual void Resume(Event @event) {
Environment.ActiveProcess = this;
while (true) {
if (@event.IsOk) {
if (generator.MoveNext()) {
if (IsTriggered) {
// the generator called e.g. Environment.ActiveProcess.Fail
Environment.ActiveProcess = null;
return;
}
if (ProceedToEvent()) break;
} else if (!IsTriggered) {
Succeed(@event.Value);
break;
} else break;
} else {
/* Fault handling differs from SimPy as in .NET it is not possible to inject an
* exception into an enumerator and it is impossible to put a yield return inside
* a try-catch block. In SimSharp the Process will set IsOk and will then move to
* the next yield in the generator. However, if after this move IsOk is still false
* we know that the error was not handled. It is assumed the error is handled if
* HandleFault() is called on the environment's ActiveProcess which will reset the
* flag. */
IsOk = false;
Value = @event.Value;
if (generator.MoveNext()) {
if (IsTriggered) {
// the generator called e.g. Environment.ActiveProcess.Fail
Environment.ActiveProcess = null;
return;
}
// if we move next, but IsOk is still false
if (!IsOk) throw new InvalidOperationException("The process did not react to being faulted.");
// otherwise HandleFault was called and the fault was handled
if (ProceedToEvent()) break;
} else if (!IsTriggered) {
if (!IsOk) Fail(@event.Value);
else Succeed(@event.Value);
break;
} else break;
}
}
Environment.ActiveProcess = null;
}
protected virtual bool ProceedToEvent() {
target = generator.Current;
Value = target.Value;
if (target.IsProcessed) return false;
target.AddCallback(Resume);
return true;
}
///
/// This method must be called to reset the IsOk flag of the process back to true.
/// The IsOk flag may be set to false if the process waited on an event that failed.
///
///
/// In SimPy a faulting process would throw an exception which is then catched and
/// chained. In SimSharp catching exceptions from a yield is not possible as a yield
/// return statement may not throw an exception.
/// If a processes faulted the Value property may indicate a cause for the fault.
///
/// True if a faulting situation needs to be handled, false if the process
/// is okay and the last yielded event succeeded.
public virtual bool HandleFault() {
if (IsOk) return false;
IsOk = true;
return true;
}
private class Initialize : Event {
public Initialize(Environment environment, Process process)
: base(environment) {
CallbackList.Add(process.Resume);
IsOk = true;
IsTriggered = true;
environment.Schedule(this);
}
}
}
}