DESim4J is a Java port of SimPy, process-based discrete event simulation framework.
DESim4J aims to port the concepts used in SimPy to the Java world. Because there is no yield
keyword in Java, the framework also implements a yield-like API in package uia.cor
to meet some coroutine scenarios.
The ROAD is a sub-project that build a abstract simulator of the manufacturing factory.
The package provides yield-like API. The main concept is
Generator gen = Yield.accept(yield -> ::function);
-
Yield
- yield.call(Object) - pass a value to paired
Generator
. - yield.close() - stop iteration.
- yield.call(Object) - pass a value to paired
-
Generator
- gen.next() - notify ::function to prepare next value.
- gen.errorNext() - notify ::function to prepare next value.
- gen.getValue() - Get the value from
yield.setValue(Object)
- gen.error(ex) - send back an exception.
- gen.send(Object) - send back a value to yield.
- gen.close() - stop yield iterable.
Below is a simple workflow of Yield-Generator:
public class YieldTest {
@Test
public void testCallFor() {
// 1
Generator<Integer> gen = Yield.accept(this::callFor);
// 2, 5
while(gen.next()) {
// 4
System.out.println("value=" + gen.getValue());
}
}
/**
* iterable work
*/
public void callFor(Yield<Integer> yield) {
for(int i = 0; i < 10; i++) {
// 3
yield.call(i);
}
}
}
Generator<Integer> gen = Yield.accept(this::callFor)
- Create aYield
object and pass tocallFor
method. Return pairedGenerator
.gen.next()
- Ask if there is a new value or not.yield.call(i)
- Pass a new value to the generator and block until invokinggen.next()
again.gen.getValue()
- Get the new value passed byyield.call(i)
.while(gen.next())
- Repeat until completing thefor
loop.
Use Yield2Way
if iteration needs to get a result from yield.call(value)
.
public class Yield2WayTest {
@Test
public void testSum2() {
Generator2Way<Integer, Integer> gen = Yield2Way.accept(this::sum2);
int i = 0;
// 4
while(gen.next()) {
// 2
i = gen.getValue();
System.out.println("value=" + i);
// 3
gen.send(i * i);
}
}
/**
* iterable work
*/
public void sum2(Yield2Way<Integer, Integer> yield) {
int i = 1;
int sum = 0;
while(i <= 10) {
// 1, 5
int v = yield.call(i++); // waiting a result
sum += v;
}
System.out.println(" sum=" + sum);
}
}
yield.call(i++)
- Pass a new value to the generator and block until invokinggen.next()
again.gen.getValue()
- Get the new value passed byyield.call(i)
.gen.send(i * i)
- Send back a result but step 1 is still blocking.gen.next()
- Ask if there is a new value or not and release step 1 at the same time.int v = yield.call(i++)
- Get the result passed bygen.send(i * i)
.
classDiagram
Generator --> Yield
Consumer ..> Yield
Yield <-- Yieldable
<<Iterable>> Consumer
Generator: next() boolean
Generator: next(R) boolean
Generator: getValue() T
Yield: call(T)
Yield: send(R)
Yield: next(boolean stop) boolean
Yield: getValue() T
Consumer: accept(Yield)
Yieldable: run()
sequenceDiagram
autonumber
Controller --) Consumer: new()
Controller ->> +Yield: accept()
Yield -->> +Generator: new()
Yield --) -Controller: generator
Yield --) Consumer: accept()
note over Yield: in a new Thread
loop thread-iterable
Consumer ->> +Yield: call(T)
note right of Consumer: send a value to the Controller
Yield ->> Yield: notifyAll()
note over Yield: notify Step-14 to get next value
Yield --) Yield: wait()
Yield --) -Consumer:
note over Yield: wait Step-12 to notify
end
loop thread-main
Controller ->> Generator: next()
Generator ->> +Yield: next()
Yield ->> Yield: notifyAll()
note over Yield: notify Step-9 to build next value
Yield --) Yield: wait()
Yield --) -Generator:
note over Yield: wait Step-7 to notify
Controller ->> Generator: T getValue()
Generator ->> +Yield: T getValue()
Yield --) -Generator: value
note right of Controller: get last value from the Generator
end
The package is core framework of process-based discrete event simulation.
Some documents
- Create a event stream
flowchart LR;
id1[[*E00]]-->E20;
E20-->E50;
E50-->E90;
- Execute head event E00, and create a new event E55. The event stream becomes
flowchart LR;
id1[[*E20]]-->E50;
E50-->id2([E55]);
id2([E55])-->E90;
- Execute head event E20, and create a new event E53. The event stream becomes
flowchart LR;
id1[[*E50]]-->id2([E53]);
id2([E53])-->id3([E55]);
id3([E55])-->E90;
- Execute all events with ordering.
flowchart LR;
id1[[*E53]]-->id2([E55]);
id2([E55])-->E90;
flowchart LR;
id1[[*E55]]-->E90;
flowchart LR;
id1[[*E90]];
classDiagram
Env --* Job
Initialize --|> Event
Job --> Event
Event <.. Yield
Event -- * Callback
Callback -- Process
Event <|-- Process
Event <.. Processable
Yield <-- Consumer
Yield <-- Generator
Consumer <|-- Processable
Processable --> Process
Generator <-- Process
<<coroutine>> Yield
<<coroutine>> Generator
<<coroutine>> Consumer
<<case>> Processable
Env: PriorityBlockingQueue~Job~ jobs
Process: +resume(Event)
Processable: +initial()
Processable: +run()
Event: List callables
Event: +callback()
-
Work flow of events
sequenceDiagram autonumber loop Env ->> +Env: poll() Env ->> Event: callback() Event ->> Process: resume(Event) Process --) Processable: // notify Processable ->> Env: // schedule a new event if needed Env --) Event: // new Env ->> -Env: // add a new event into the queue Processable --) Process: // notify Process ->> Event: addCallable() end
-
Startup of a process
sequenceDiagram autonumber Env ->> Event["Initialize"]: callback() Event["Initialize"] ->> +Process: resume(Event) Process ->> Event["Initialize"]: getValue() Process ->> +Generator: next(Object) Generator ->> Yield: send(Object) Generator ->> Yield: next(Boolean) Yield --) Processable: notifyAll() Processable ->> +Env: // schedule a new event if needed Env --) Event: new Env ->> -Env: // add a new event into the queue Processable ->> Yield: call(Event) Yield --) Generator: notifyAll() Generator --) -Process: Process ->> Generator: getValue() Process ->> -Event: addCallable(Process::resume) // hook the Process and Event together
Below is a Java test case compares with Python version.
class School:
def __init__(self, env):
self.env = env
self.class_ends = env.event()
self.pupil_procs = [env.process(self.pupil()) for i in range(3)]
self.bell_proc = env.process(self.bell())
def bell(self):
while True:
yield self.env.timeout(45)
self.class_ends.succeed()
self.class_ends = self.env.event()
print()
def pupil(self):
while True:
yield self.class_ends
print(r' \o/', end='')
env = Environment()
school = School(env)
env.run(200)
public class SchoolTest {
private Env env;
private Event classEnd;
public SchoolTest() {
this.env = new Env();
this.classEnd = this.env.event("classEnd");
env.process("pupil-1", this::pupil);
env.process("pupil-2", this::pupil);
env.process("pupil-3", this::pupil);
env.process("bell", this::bell);
}
public void bell(Yield<Event> yield) {
while(yield.isAlive()) {
yield.call(env.timeout(45));
this.classEnd.succeed(null);
this.classEnd = this.env.event("classEnd");
System.out.println(String.format("\n%3d> bell is ringing...", this.env.getNow()));
}
}
public void pupil(Yield<Event> yield) {
while(yield.isAlive()) {
yield.call(this.classEnd);
System.out.print("\\o/ ");
}
}
@Test
public void test1() throws Exception {
this.env.run(200);
}
}
The framework is still building and testing. The next tasks are
- Add
resources
implementation. - More stable of
uia.cor
package. - More reasonable Exception control.
- More test cases to prove the framework.