yog-event 0.8.0

Yog event system — pub/sub for game/server events.
Documentation
//! A record's view in Java (`Name.View`): the record read where its frame
//! is — the arena, in place — each field decoded only when it's asked for.
//! Made, it has stepped over the fields once (lists and records by their
//! lengths) and kept where each starts; a nested record is a view too, a
//! list of numbers is read item by item (`x(i)`, `xLength()`) without being
//! unpacked, a list of records is walked (`forEachX`). Everything else is
//! decoded as the record's own codec does, from where it starts.
//!
//! A view shares its reader with the views it hands out, so it's read on
//! the thread that made it, before that thread's next bus call.

use std::fmt::Write;

use yog_data::Shape;
use yog_data::schema::StructShape;

use super::field;
use super::frames::{missing, unpack};
use super::types::{F32_BITS, TYPES, fits_int, java_type};

/// How a field is kept by the view.
enum Kept<'a> {
    /// Numbers bit-packed: read one by one.
    Ints { signed: bool, wide: bool },
    /// Floats: read one by one (`f32`: a list of them, never widened).
    Floats { f32: bool },
    /// A record: viewed.
    Record(&'a StructShape),
    /// A list of records: walked.
    Records(&'a StructShape),
    /// Anything else: decoded when asked.
    Decoded,
}

fn kept<'a>(shape: &'a Shape, item: &'a Option<Shape>) -> Kept<'a> {
    match (shape, item) {
        (Shape::Struct(s), _) => Kept::Record(s),
        (Shape::List(_), Some(Shape::Int { min, max })) => Kept::Ints {
            signed: *min < 0,
            wide: !fits_int(*min, *max),
        },
        (Shape::List(_), Some(Shape::Float { bits })) => Kept::Floats {
            f32: *bits == F32_BITS,
        },
        (Shape::List(_), Some(Shape::Struct(s))) => Kept::Records(s),
        _ => Kept::Decoded,
    }
}

/// `Name.View` for record `s`, indented by `pad`.
pub(super) fn view(name: &str, s: &StructShape, pad: &str) -> String {
    let fields: Vec<(String, Shape, Option<Shape>)> = s
        .fields
        .iter()
        .map(|f| {
            let shape = (f.shape)();
            let item = if let Shape::List(i) = &shape {
                Some(i())
            } else {
                None
            };
            (field(f.name), shape, item)
        })
        .collect();
    let mut out = String::new();
    let _ = write!(
        out,
        "\n\n{pad}    /** {{@code {name}}} read where its frame is, each field when it's asked for ({{@code yog-event}}'s {{@code java/views.rs}}). */\n{pad}    public static final class View {{\n{pad}        private final YogFrame.Unpacker u;"
    );
    for (k, (_, shape, item)) in fields.iter().enumerate() {
        let _ = write!(out, "\n{pad}        private final int o{k};");
        if matches!(
            kept(shape, item),
            Kept::Ints { .. } | Kept::Floats { .. } | Kept::Records(_)
        ) {
            let _ = write!(out, "\n{pad}        private final int h{k}, n{k}, b{k};");
        }
    }
    let _ = write!(
        out,
        "\n\n{pad}        public View(YogFrame.Unpacker u) {{\n{pad}            this.u = u;\n{pad}            int end = u.begin();"
    );
    for (k, (_, shape, item)) in fields.iter().enumerate() {
        let _ = write!(out, "\n{pad}            {}", step(k, shape, item));
    }
    let _ = write!(out, "\n{pad}            u.leave(end);\n{pad}        }}");
    for (k, (f, shape, item)) in fields.iter().enumerate() {
        out.push_str(&accessors(k, f, shape, item, pad));
    }
    let _ = write!(out, "\n{pad}    }}");
    out
}

/// The constructor's statements for field `k`: where it starts, stepped over.
fn step(k: usize, shape: &Shape, item: &Option<Shape>) -> String {
    let skip = match kept(shape, item) {
        Kept::Ints { .. } => {
            return format!(
                "if (u.more(end)) {{ h{k} = u.position(); n{k} = (int) u.getU(); b{k} = u.getByte(); o{k} = u.position(); u.skip(YogFrame.packedBytes(n{k}, b{k})); }} else {{ h{k} = n{k} = b{k} = 0; o{k} = -1; }}"
            );
        }
        Kept::Floats { f32 } => {
            let narrow = if f32 {
                "1".to_owned()
            } else {
                "u.getBool() ? 1 : 0".to_owned()
            };
            return format!(
                "if (u.more(end)) {{ h{k} = u.position(); n{k} = u.count(); b{k} = {narrow}; o{k} = u.position(); u.skip(n{k} * (b{k} != 0 ? 4 : 8)); }} else {{ h{k} = n{k} = b{k} = 0; o{k} = -1; }}"
            );
        }
        Kept::Records(_) => {
            return format!(
                "if (u.more(end)) {{ h{k} = u.position(); n{k} = u.count(); int e{k} = u.listEnd(); b{k} = 0; o{k} = u.position(); u.skipTo(e{k}); }} else {{ h{k} = n{k} = b{k} = 0; o{k} = -1; }}"
            );
        }
        Kept::Record(_) | Kept::Decoded => skip(shape, 0),
    };
    format!("if (u.more(end)) {{ o{k} = u.position(); {skip} }} else {{ o{k} = -1; }}")
}

/// Statements stepping over a value of `shape`; `depth` names locals apart.
fn skip(shape: &Shape, depth: usize) -> String {
    match shape {
        Shape::Bool => "u.skip(1);".into(),
        Shape::Int { .. } | Shape::Str | Shape::Atom => "u.getU();".into(),
        Shape::Float { bits } if *bits == F32_BITS => "u.skip(4);".into(),
        Shape::Float { .. } => "u.skip(8);".into(),
        Shape::Enum(en) if en.tag.is_none() => "u.getU();".into(),
        Shape::Optional(inner) => format!("if (u.getBool()) {{ {} }}", skip(&inner(), depth + 1)),
        Shape::Struct(_) => format!("{{ int r{depth} = u.begin(); u.leave(r{depth}); }}"),
        Shape::List(item) => match item() {
            Shape::Int { .. } => format!(
                "{{ int c{depth} = (int) u.getU(); int w{depth} = u.getByte(); u.skip(YogFrame.packedBytes(c{depth}, w{depth})); }}"
            ),
            Shape::Float { bits } if bits == F32_BITS => "u.skip(u.count() * 4);".into(),
            Shape::Float { .. } => {
                format!("{{ int c{depth} = u.count(); u.skip(c{depth} * (u.getBool() ? 4 : 8)); }}")
            }
            _ => "{ u.count(); u.skipTo(u.listEnd()); }".into(),
        },
        // Bytes, and what goes as its binary form.
        _ => "u.skipBytes();".into(),
    }
}

/// Field `k`'s methods.
fn accessors(k: usize, f: &str, shape: &Shape, item: &Option<Shape>, pad: &str) -> String {
    let java = java_type(shape, &mut Vec::new());
    let big = format!("{}{}", f[..1].to_uppercase(), &f[1..]);
    let decoded = |out: &mut String, from: &str| {
        let _ = write!(
            out,
            "\n\n{pad}        public {java} {f}() {{\n{pad}            if (o{k} < 0) return {};\n{pad}            u.seek({from});\n{pad}            return {};\n{pad}        }}",
            missing(shape),
            unpack(shape, 0)
        );
    };
    let mut out = String::new();
    match kept(shape, item) {
        Kept::Record(s) => {
            let _ = write!(
                out,
                "\n\n{pad}        public {TYPES}.{n}.View {f}() {{\n{pad}            if (o{k} < 0) return null;\n{pad}            u.seek(o{k});\n{pad}            return new {TYPES}.{n}.View(u);\n{pad}        }}",
                n = s.name
            );
        }
        Kept::Ints { signed, wide } => {
            decoded(&mut out, &format!("h{k}"));
            let (t, raw) = (
                if wide { "long" } else { "int" },
                if signed { "YogFrame.unzigzag(v)" } else { "v" },
            );
            let _ = write!(
                out,
                "\n\n{pad}        public int {f}Length() {{\n{pad}            return n{k};\n{pad}        }}\n\n{pad}        /** Item {{@code i}}, read in place. */\n{pad}        public {t} {f}(int i) {{\n{pad}            long v = u.packedAt(o{k}, n{k}, b{k}, i);\n{pad}            return ({t}) {raw};\n{pad}        }}"
            );
        }
        Kept::Floats { f32 } => {
            decoded(&mut out, &format!("h{k}"));
            let (t, read) = if f32 {
                ("float", format!("u.f32At(o{k} + 4 * i)"))
            } else {
                (
                    "double",
                    format!("b{k} != 0 ? (double) u.f32At(o{k} + 4 * i) : u.f64At(o{k} + 8 * i)"),
                )
            };
            let _ = write!(
                out,
                "\n\n{pad}        public int {f}Length() {{\n{pad}            return n{k};\n{pad}        }}\n\n{pad}        /** Item {{@code i}}, read in place. */\n{pad}        public {t} {f}(int i) {{\n{pad}            if (i < 0 || i >= n{k}) throw new IndexOutOfBoundsException(i);\n{pad}            return {read};\n{pad}        }}"
            );
        }
        Kept::Records(s) => {
            decoded(&mut out, &format!("h{k}"));
            let _ = write!(
                out,
                "\n\n{pad}        public int {f}Length() {{\n{pad}            return n{k};\n{pad}        }}\n\n{pad}        /** Each item, viewed in turn. */\n{pad}        public void forEach{big}(java.util.function.Consumer<{TYPES}.{n}.View> each) {{\n{pad}            int at = o{k};\n{pad}            for (int j = 0; j < n{k}; j++) {{\n{pad}                u.seek(at);\n{pad}                {TYPES}.{n}.View v = new {TYPES}.{n}.View(u);\n{pad}                at = u.position();\n{pad}                each.accept(v);\n{pad}            }}\n{pad}        }}",
                n = s.name
            );
        }
        Kept::Decoded => decoded(&mut out, &format!("o{k}")),
    }
    out
}