feat(core): programs' threads, and FILIUS' echo server and generic client after Java, measured against Java

Programs run as generators that yield what they wait for (threads.ts); the
engine resumes them after every event. TCP connect, send, receive, accept
and name resolution exist as such steps; the blocking calls wrap them.
EchoRun records eight stories in Java FILIUS, frames, tables and each
program's window lines; echoJava.test.ts plays them card by card. Not
taken from Java: a connection without a route hangs for ever; a server
switched off leaves its connections' last FIN unanswered.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-01 16:27:44 +00:00
co-authored by Claude Opus 5.5
parent 0abce60aef
commit de3a1d4dfe
24 changed files with 13454 additions and 28 deletions
+188
View File
@@ -0,0 +1,188 @@
// Runs a story of FILIUS' echo server (ServerBaustein) and generic client
// (ClientBaustein) on a core scenario's network in Java FILIUS
// (ScenarioNetwork) and records what really happens: every frame each card
// saw and every line each program wrote into its window (its observers).
//
// EchoRun <scenario.json> <story> <milliseconds> <out.json>
//
// The stories use the network of tcp-lan (server 192.168.0.10, clients
// 192.168.0.20 and .30). The server program runs on .10, switched on
// (aktiv) and on its port 55555 before the simulation starts, as a file
// loads it; each client program on .20 and .30. The calls come as the
// windows make them: verbinden and trennen hand over to the program's
// thread, senden runs in the caller's thread (the window's) until it returns.
// exchange .20 connects, sends "Hallo" and "Welt", disconnects
// closedPort .20 connects to .30, where no server runs
// noRoute .20 connects to 10.0.0.1, outside its network, no gateway
// notConnected .20 sends without a connection
// serverOff connected, the server is switched off, .20 sends; the
// server is switched on again, .20 connects and sends
// reconnect .20 connects, disconnects, connects again and sends
// twoClients .20 and .30 connected at once, each sends, each disconnects
// otherPort the server switched off, a second later over to port 7 and
// on (as its window does it); .20 connects to 7 and sends, .30
// connects to 55555 (no longer served)
import filius.software.clientserver.ClientBaustein;
import filius.software.clientserver.ServerBaustein;
import java.util.ArrayList;
import java.util.List;
public class EchoRun {
static final String SERVER = "00000000-0000-0000-0000-000000000101";
static final String CLIENT = "00000000-0000-0000-0000-000000000102";
static final String OTHER_CLIENT = "00000000-0000-0000-0000-000000000103";
static final String SERVER_CLASS = "filius.software.clientserver.ServerBaustein";
static final String CLIENT_CLASS = "filius.software.clientserver.ClientBaustein";
static ScenarioNetwork network;
static final List<Object[]> events = new ArrayList<>();
static synchronized void event(String who, String what) {
events.add(new Object[] { System.currentTimeMillis() - network.start, who, what });
}
static void sleepUntil(long milliseconds) throws InterruptedException {
long rest = network.start + milliseconds - System.currentTimeMillis();
if (rest > 0) Thread.sleep(rest);
}
static ClientBaustein client(String nodeId) {
return (ClientBaustein) network.system(nodeId).holeSoftware(CLIENT_CLASS);
}
static void connect(String who, ClientBaustein client, String address, int port) {
event(who, "call verbinden " + address + ":" + port);
client.setZielIPAdresse(address);
client.setZielPort(port);
client.verbinden();
}
static void send(String who, ClientBaustein client, String text) {
event(who, "call senden " + text);
client.senden(text);
event(who, "returned senden");
}
static void disconnect(String who, ClientBaustein client) {
event(who, "call trennen");
client.trennen();
}
public static void main(String[] args) throws Exception {
network = ScenarioNetwork.read(args[0]);
String story = args[1];
long end = Long.parseLong(args[2]);
network.system(SERVER).installApp(SERVER_CLASS, false);
network.system(CLIENT).installApp(CLIENT_CLASS, false);
network.system(OTHER_CLIENT).installApp(CLIENT_CLASS, false);
ServerBaustein server = (ServerBaustein) network.system(SERVER).holeSoftware(SERVER_CLASS);
server.setPort(55555);
server.setAktiv(true);
// The windows: opened after loading, so they see what follows.
server.hinzuBeobachter((observable, data) -> event("server", String.valueOf(data)));
client(CLIENT).hinzuBeobachter((observable, data) -> event("client", String.valueOf(data)));
client(OTHER_CLIENT).hinzuBeobachter((observable, data) -> event("other", String.valueOf(data)));
network.start();
ClientBaustein client = client(CLIENT);
ClientBaustein other = client(OTHER_CLIENT);
switch (story) {
case "exchange":
sleepUntil(500);
connect("client", client, "192.168.0.10", 55555);
sleepUntil(2000);
send("client", client, "Hallo");
sleepUntil(4000);
send("client", client, "Welt");
sleepUntil(6000);
disconnect("client", client);
break;
case "closedPort":
sleepUntil(500);
connect("client", client, "192.168.0.30", 55555);
break;
case "noRoute":
sleepUntil(500);
connect("client", client, "10.0.0.1", 55555);
break;
case "notConnected":
sleepUntil(500);
send("client", client, "Hallo");
break;
case "serverOff":
sleepUntil(500);
connect("client", client, "192.168.0.10", 55555);
sleepUntil(2000);
event("server", "call setAktiv false");
server.setAktiv(false);
sleepUntil(4000);
send("client", client, "Hallo");
sleepUntil(6000);
event("server", "call setAktiv true");
server.setAktiv(true);
sleepUntil(7000);
connect("client", client, "192.168.0.10", 55555);
sleepUntil(9000);
send("client", client, "Welt");
break;
case "reconnect":
sleepUntil(500);
connect("client", client, "192.168.0.10", 55555);
sleepUntil(2000);
disconnect("client", client);
sleepUntil(4000);
connect("client", client, "192.168.0.10", 55555);
sleepUntil(6000);
send("client", client, "Hallo");
break;
case "twoClients":
sleepUntil(500);
connect("client", client, "192.168.0.10", 55555);
sleepUntil(1000);
connect("other", other, "192.168.0.10", 55555);
sleepUntil(2000);
send("client", client, "eins");
sleepUntil(3000);
send("other", other, "zwei");
sleepUntil(4000);
disconnect("client", client);
sleepUntil(5000);
disconnect("other", other);
break;
case "otherPort":
sleepUntil(500);
event("server", "call setAktiv false");
server.setAktiv(false);
sleepUntil(1500);
event("server", "call setPort 7");
server.setPort(7);
event("server", "call setAktiv true");
server.setAktiv(true);
sleepUntil(2000);
connect("client", client, "192.168.0.10", 7);
sleepUntil(4000);
send("client", client, "Hallo");
sleepUntil(6000);
connect("other", other, "192.168.0.10", 55555);
break;
default:
throw new IllegalArgumentException("unknown story " + story);
}
sleepUntil(end);
ScenarioNetwork.Json out = new ScenarioNetwork.Json();
out.open('{');
network.writeFrames(out);
synchronized (EchoRun.class) {
out.key("events").open('[');
for (Object[] each : events) out.open('[').number((Long) each[0]).string((String) each[1]).string((String) each[2]).close(']');
out.close(']');
}
out.close('}');
network.stop();
ScenarioNetwork.write(args[3], out);
System.exit(0);
}
}