elura-netcode 0.2.6

Transport-independent tick synchronization and redundant input primitives for Elura
Documentation
use elura_netcode::{
    InputAck, InputFrame, InputPacket, InputReceiver, InputReceiverConfig, InputSender,
    InputSenderConfig, NetcodeError, SequenceDisposition, SequenceWindow,
};

fn packet(inputs: Vec<InputFrame<&'static str>>) -> InputPacket<&'static str> {
    InputPacket {
        client_tick: 20,
        acknowledged_server_tick: 0,
        inputs,
    }
}

#[test]
fn sender_repeats_recent_unacknowledged_inputs() {
    let mut config = InputSenderConfig::default();
    config.history_capacity = 8;
    config.redundancy = 3;
    let mut sender = InputSender::new(config).unwrap();
    for tick in 10..=13 {
        sender.record(tick, tick).unwrap();
    }

    let packet = sender.packet(9);
    assert_eq!(
        packet
            .inputs
            .iter()
            .map(|frame| frame.sequence)
            .collect::<Vec<_>>(),
        vec![2, 3, 4]
    );
    assert_eq!(
        packet
            .inputs
            .iter()
            .map(|frame| frame.target_tick)
            .collect::<Vec<_>>(),
        vec![11, 12, 13]
    );
}

#[test]
fn acknowledgement_releases_history_and_rejects_impossible_values() {
    let mut sender = InputSender::new(InputSenderConfig::default()).unwrap();
    sender.record(10, "a").unwrap();
    sender.record(11, "b").unwrap();

    assert_eq!(
        sender
            .acknowledge(InputAck {
                server_tick: 8,
                acknowledged_sequence: 1,
            })
            .unwrap(),
        1
    );
    assert_eq!(sender.pending_len(), 1);
    assert_eq!(sender.acknowledged_server_tick(), 8);
    assert_eq!(
        sender.acknowledge(InputAck {
            server_tick: 9,
            acknowledged_sequence: 3,
        }),
        Err(NetcodeError::InvalidAcknowledgement)
    );
}

#[test]
fn sender_never_drops_unacknowledged_input_silently() {
    let mut config = InputSenderConfig::default();
    config.history_capacity = 2;
    config.redundancy = 2;
    let mut sender = InputSender::new(config).unwrap();
    sender.record(1, ()).unwrap();
    sender.record(2, ()).unwrap();
    assert_eq!(sender.record(3, ()), Err(NetcodeError::InputHistoryFull));
}

#[test]
fn sequence_window_closes_out_of_order_gaps() {
    let mut window = SequenceWindow::new(8).unwrap();
    assert_eq!(window.observe(2).unwrap(), SequenceDisposition::Accepted);
    assert_eq!(window.acknowledged(), 0);
    assert_eq!(window.pending_len(), 1);

    assert_eq!(window.observe(1).unwrap(), SequenceDisposition::Accepted);
    assert_eq!(window.acknowledged(), 2);
    assert_eq!(window.pending_len(), 0);
    assert_eq!(window.observe(1).unwrap(), SequenceDisposition::Duplicate);
}

#[test]
fn receiver_is_order_independent_and_returns_cumulative_ack() {
    let mut receiver = InputReceiver::new(InputReceiverConfig::default()).unwrap();
    let first = receiver
        .receive(
            20,
            packet(vec![InputFrame {
                sequence: 2,
                target_tick: 22,
                input: "second",
            }]),
        )
        .unwrap();
    assert_eq!(first.acknowledgement.acknowledged_sequence, 0);
    assert_eq!(first.accepted.len(), 1);

    let second = receiver
        .receive(
            21,
            packet(vec![InputFrame {
                sequence: 1,
                target_tick: 21,
                input: "first",
            }]),
        )
        .unwrap();
    assert_eq!(second.acknowledgement.acknowledged_sequence, 2);
    assert_eq!(second.accepted[0].input, "first");
}

#[test]
fn old_redundant_frames_are_ignored_after_tick_window_moves() {
    let mut config = InputReceiverConfig::default();
    config.max_past_ticks = 1;
    let mut receiver = InputReceiver::new(config).unwrap();
    let old = InputFrame {
        sequence: 1,
        target_tick: 10,
        input: "move",
    };
    receiver.receive(10, packet(vec![old.clone()])).unwrap();

    let report = receiver.receive(100, packet(vec![old])).unwrap();
    assert!(report.accepted.is_empty());
    assert_eq!(report.duplicates, 1);
}

#[test]
fn invalid_new_frame_does_not_partially_advance_receiver() {
    let mut config = InputReceiverConfig::default();
    config.max_future_ticks = 2;
    let mut receiver = InputReceiver::new(config).unwrap();
    let result = receiver.receive(
        10,
        packet(vec![
            InputFrame {
                sequence: 1,
                target_tick: 11,
                input: "valid",
            },
            InputFrame {
                sequence: 2,
                target_tick: 20,
                input: "future",
            },
        ]),
    );
    assert!(matches!(result, Err(NetcodeError::InvalidInput(_))));
    assert_eq!(receiver.acknowledged_sequence(), 0);
    assert_eq!(receiver.pending_sequences(), 0);
}

#[test]
fn one_lost_packet_is_recovered_by_the_next_redundant_packet() {
    let mut config = InputSenderConfig::default();
    config.history_capacity = 8;
    config.redundancy = 3;
    let mut sender = InputSender::new(config).unwrap();
    let mut receiver = InputReceiver::new(InputReceiverConfig::default()).unwrap();

    sender.record(11, "left").unwrap();
    let _lost = sender.packet(8);
    sender.record(12, "right").unwrap();

    let report = receiver.receive(10, sender.packet(9)).unwrap();
    assert_eq!(report.accepted.len(), 2);
    assert_eq!(report.acknowledgement.acknowledged_sequence, 2);
    sender.acknowledge(report.acknowledgement).unwrap();
    assert!(sender.is_empty());
}