///|
/// Simulation package for M1-CORE-P2P.
///|
pub fn package_id() -> String {
"lockwire/sim"
}
///|
pub(all) struct BenchmarkSample {
events_per_run : Int
nominal_events_per_second : Int
copies_per_frame : Int
} derive(Eq, Debug)
///|
pub(all) struct SimRun {
seed : Int
events : Array[@core.RxEvent]
digest : @core.SimDigest
benchmark : BenchmarkSample
} derive(Eq, Debug)
///|
pub(all) struct P2pLink {
a : @core.EndpointId
b : @core.EndpointId
delay : @core.Duration
} derive(Eq, Debug)
///|
pub fn P2pLink::new(
a~ : @core.EndpointId,
b~ : @core.EndpointId,
delay~ : @core.Duration,
) -> P2pLink {
{ a, b, delay }
}
///|
fn P2pLink::deliver_bound(
self : P2pLink,
ev : @core.TxEvent,
) -> Array[@core.RxEvent] {
let rx : @core.RxEvent = {
id: ev.id + 1000,
parent_id: ev.id,
vtime: ev.vtime.add(self.delay),
channel_id: ev.channel_id,
seq: ev.seq,
source: ev.source,
target: ev.target,
frame: ev.frame.sending().sent().receiving().received(),
}
[rx]
}
///|
pub impl @core.Medium for P2pLink with fn submit(self, ev) {
if (ev.source == self.a && ev.target == self.b) ||
(ev.source == self.b && ev.target == self.a) {
self.deliver_bound(ev)
} else {
raise @core.TransportError::UnboundEndpoint(ev.target)
}
}
///|
pub impl @core.Medium for P2pLink with fn lookahead(self) {
self.delay
}
///|
pub impl @core.PointToPointMedium for P2pLink with fn bind(_self, _a, _b) {
()
}
///|
pub fn run_p2p_demo(seed~ : Int) -> SimRun {
let left = @core.EndpointId(1)
let right = @core.EndpointId(2)
let link = P2pLink::new(a=left, b=right, delay=@core.Duration::from_ns(5L))
let frame_a = @core.Frame::acquire(id=1).filled(payload=b"a")
let frame_b = @core.Frame::acquire(id=2).filled(payload=b"b")
let first = @core.TxEvent::make(
id=1,
vtime=@core.VTime::from_ns(0L),
channel_id=@core.ChannelId(1),
seq=1,
source=left,
target=right,
frame=frame_a,
)
let second = @core.TxEvent::make(
id=2,
vtime=@core.VTime::from_ns(0L),
channel_id=@core.ChannelId(2),
seq=1,
source=right,
target=left,
frame=frame_b,
)
let ordered = if seed % 2 == 0 { [first, second] } else { [second, first] }
let mut digest = @core.SimDigest::empty(seed~).step_rng()
let events : Array[@core.RxEvent] = []
for tx in ordered {
let rx_events = link.deliver_bound(tx)
for rx in rx_events {
events.push(rx)
digest = digest
.mix(rx.id)
.mix(rx.parent_id)
.mix(rx.channel_id.value())
.mix(rx.seq)
}
}
{
seed,
events,
digest,
benchmark: {
events_per_run: events.length(),
nominal_events_per_second: events.length(),
copies_per_frame: 0,
},
}
}
///|
pub fn same_seed_digest_stable(seed~ : Int) -> Bool {
run_p2p_demo(seed~).digest == run_p2p_demo(seed~).digest
}
///|
pub fn SimRun::to_trace_log(
self : SimRun,
backend~ : @core.BackendProfile,
) -> @trace.TraceLog {
let log = @trace.TraceLog::new()
for rx in self.events {
log.append(
@trace.rx_event_to_trace(
rx,
seed=self.seed,
rng_step=self.digest.rng_steps,
clock_domain="sim",
node_id="p2p",
medium_id="p2p",
backend~,
),
)
}
log
}
///|
pub fn SimRun::benchmark_report(
self : SimRun,
scenario~ : String,
backend~ : @core.BackendProfile,
) -> @trace.RuntimeBenchmarkReport {
self
.to_trace_log(backend~)
.benchmark_report(
scenario~,
seed=self.seed,
backend~,
copies_per_frame=self.benchmark.copies_per_frame,
)
}
///|
pub fn p2p_benchmark_report(
seed~ : Int,
backend~ : @core.BackendProfile,
) -> @trace.RuntimeBenchmarkReport {
run_p2p_demo(seed~).benchmark_report(scenario="p2p", backend~)
}
///|
pub(all) struct SegmentNode {
id : @core.EndpointId
name : String
port_delay : @core.Duration
window_offset : Int
window_length : Int
rewrite_value : Byte?
} derive(Eq, Debug)
///|
pub fn SegmentNode::new(
id~ : @core.EndpointId,
name~ : String,
port_delay~ : @core.Duration,
) -> SegmentNode {
{
id,
name,
port_delay,
window_offset: 0,
window_length: 0,
rewrite_value: None,
}
}
///|
pub fn SegmentNode::with_window_write(
self : SegmentNode,
offset~ : Int,
length~ : Int,
value~ : Byte,
) -> SegmentNode {
{
id: self.id,
name: self.name,
port_delay: self.port_delay,
window_offset: offset,
window_length: length,
rewrite_value: Some(value),
}
}
///|
pub(all) struct FrameWindow {
node_id : @core.EndpointId
offset : Int
length : Int
before_digest : Int
after_digest : Int
} derive(Eq, Debug)
///|
pub(all) struct SegmentHop {
index : Int
node_id : @core.EndpointId
node_name : String
arrive : @core.VTime
depart : @core.VTime
port_delay : @core.Duration
window : FrameWindow
} derive(Eq, Debug)
///|
pub(all) struct SegmentRun {
seed : Int
line_name : String
hops : Array[SegmentHop]
final_payload : Bytes
total_delay : @core.Duration
digest : @core.SimDigest
} derive(Eq, Debug)
///|
pub(all) struct SegmentLine {
name : String
nodes : Array[SegmentNode]
} derive(Eq, Debug)
///|
pub fn SegmentLine::new(
name~ : String,
nodes~ : Array[SegmentNode],
) -> SegmentLine {
{ name, nodes }
}
///|
pub fn SegmentLine::run(
self : SegmentLine,
payload~ : Bytes,
start~ : @core.VTime,
seed~ : Int,
) -> SegmentRun {
let mut now = start
let mut frame = payload
let hops : Array[SegmentHop] = []
let mut digest = @core.SimDigest::empty(seed~).step_rng()
for i, node in self.nodes {
let arrive = now
let before_digest = segment_payload_digest(frame)
frame = apply_segment_window(node, frame)
let after_digest = segment_payload_digest(frame)
now = now.add(node.port_delay)
let hop : SegmentHop = {
index: i,
node_id: node.id,
node_name: node.name,
arrive,
depart: now,
port_delay: node.port_delay,
window: {
node_id: node.id,
offset: node.window_offset,
length: node.window_length,
before_digest,
after_digest,
},
}
hops.push(hop)
digest = digest
.mix(node.id.value())
.mix(arrive.ns().to_int())
.mix(now.ns().to_int())
.mix(before_digest)
.mix(after_digest)
}
{
seed,
line_name: self.name,
hops,
final_payload: frame,
total_delay: @core.Duration::from_ns(now.ns() - start.ns()),
digest,
}
}
///|
pub fn SegmentRun::timeline_text(self : SegmentRun) -> String {
let buf = StringBuilder::new()
buf.write_string("segment=")
buf.write_string(self.line_name)
buf.write_string("|seed=")
buf.write_string(self.seed.to_string())
buf.write_string("|total_delay_ns=")
buf.write_string(self.total_delay.ns().to_string())
for hop in self.hops {
buf.write_char('\n')
buf.write_string("hop=")
buf.write_string(hop.index.to_string())
buf.write_string("|node=")
buf.write_string(hop.node_name)
buf.write_string("|arrive_ns=")
buf.write_string(hop.arrive.ns().to_string())
buf.write_string("|depart_ns=")
buf.write_string(hop.depart.ns().to_string())
buf.write_string("|port_delay_ns=")
buf.write_string(hop.port_delay.ns().to_string())
buf.write_string("|before=")
buf.write_string(hop.window.before_digest.to_string())
buf.write_string("|after=")
buf.write_string(hop.window.after_digest.to_string())
}
buf.to_string()
}
///|
pub fn run_segment_toy(seed~ : Int) -> SegmentRun {
let line = SegmentLine::new(name="line-3", nodes=[
SegmentNode::new(
id=@core.EndpointId(1),
name="drive-1",
port_delay=@core.Duration::from_ns(2L),
).with_window_write(offset=0, length=1, value=b'A'),
SegmentNode::new(
id=@core.EndpointId(2),
name="drive-2",
port_delay=@core.Duration::from_ns(3L),
).with_window_write(offset=1, length=1, value=b'B'),
SegmentNode::new(
id=@core.EndpointId(3),
name="drive-3",
port_delay=@core.Duration::from_ns(5L),
),
])
line.run(payload=b"xyz", start=@core.VTime::from_ns(0L), seed~)
}
///|
fn apply_segment_window(node : SegmentNode, payload : Bytes) -> Bytes {
guard node.rewrite_value is Some(value) else { return payload }
if node.window_length <= 0 {
return payload
}
let offset = node.window_offset
if offset < 0 || offset >= payload.length() {
return payload
}
let buf = FixedArray::make(payload.length(), b'\x00')
for i in 0.. Int {
let mut digest = 23
for i in 0..