pub mod all;
pub mod helper_classes;
pub mod wire_type;
use interoptopus::inventory::{TypeId, Types as RsTypes};
use interoptopus::lang::types::{Array, Layout, Primitive, Struct, TypeKind as RsTypeKind, WireOnly};
pub struct WireCodeGen<'a> {
pub rs_types: &'a RsTypes,
}
impl WireCodeGen<'_> {
#[must_use]
pub fn cs_type_name(&self, ty_id: TypeId) -> String {
let Some(ty) = self.rs_types.get(&ty_id) else {
return "object".to_string();
};
match &ty.kind {
RsTypeKind::Primitive(p) => cs_primitive_name(*p).to_string(),
RsTypeKind::WireOnly(WireOnly::String) => "string".to_string(),
RsTypeKind::WireOnly(WireOnly::Vec(inner)) => {
format!("List<{}>", self.cs_type_name(*inner))
}
RsTypeKind::WireOnly(WireOnly::Map(k, v)) => {
format!("Dictionary<{}, {}>", self.cs_type_name(*k), self.cs_type_name(*v))
}
RsTypeKind::Struct(_) => ty.name.clone(),
RsTypeKind::Enum(_) => ty.name.clone(),
RsTypeKind::Array(arr) => format!("{}[]", self.cs_type_name(arr.ty)),
_ => "object".to_string(),
}
}
#[must_use]
pub fn serialize_struct_body(&self, s: &Struct, val: &str) -> String {
let mut lines = Vec::new();
for f in &s.fields {
let access = format!("{val}.{}", f.name);
self.emit_serialize(&mut lines, f.ty, &access, 0, 0);
}
lines.join("\n")
}
#[must_use]
pub fn deserialize_struct_body(&self, s: &Struct, type_name: &str) -> String {
let mut lines = Vec::new();
lines.push(format!("var result = new {type_name}();"));
for f in &s.fields {
let target = format!("result.{}", f.name);
self.emit_deserialize(&mut lines, f.ty, &target, 0, 0);
}
lines.push("return result;".to_string());
lines.join("\n")
}
#[must_use]
pub fn size_struct_body(&self, s: &Struct, val: &str) -> String {
let mut lines = Vec::new();
lines.push("var _size = 0;".to_string());
for f in &s.fields {
let access = format!("{val}.{}", f.name);
self.emit_size(&mut lines, f.ty, &access, 0, 0);
}
lines.push("return _size;".to_string());
lines.join("\n")
}
pub fn emit_serialize(&self, lines: &mut Vec<String>, ty_id: TypeId, val: &str, depth: usize, indent: usize) {
let Some(ty) = self.rs_types.get(&ty_id) else { return };
let p = pad(indent);
match &ty.kind {
RsTypeKind::Primitive(prim) => {
if *prim == Primitive::Bool {
lines.push(format!("{p}writer.Write({val} ? (byte)1 : (byte)0);"));
} else {
lines.push(format!("{p}writer.Write({val});"));
}
}
RsTypeKind::WireOnly(WireOnly::String) => {
lines.push(format!("{p}{{ var _bytes = System.Text.Encoding.UTF8.GetBytes({val} ?? \"\"); writer.Write((uint)_bytes.Length); writer.Write(_bytes); }}"));
}
RsTypeKind::WireOnly(WireOnly::Vec(inner_id)) => {
let iter = format!("_item{depth}");
let pi = pad(indent + 1);
lines.push(format!("{p}writer.Write((uint)({val}?.Count ?? 0));"));
lines.push(format!("{p}if ({val} != null)"));
lines.push(format!("{p}{{"));
lines.push(format!("{pi}foreach (var {iter} in {val})"));
lines.push(format!("{pi}{{"));
self.emit_serialize(lines, *inner_id, &iter, depth + 1, indent + 2);
lines.push(format!("{pi}}}"));
lines.push(format!("{p}}}"));
}
RsTypeKind::WireOnly(WireOnly::Map(k_id, v_id)) => {
let kv = format!("_kv{depth}");
let pi = pad(indent + 1);
lines.push(format!("{p}writer.Write((uint)({val}?.Count ?? 0));"));
lines.push(format!("{p}if ({val} != null)"));
lines.push(format!("{p}{{"));
lines.push(format!("{pi}foreach (var {kv} in {val})"));
lines.push(format!("{pi}{{"));
self.emit_serialize(lines, *k_id, &format!("{kv}.Key"), depth + 1, indent + 2);
self.emit_serialize(lines, *v_id, &format!("{kv}.Value"), depth + 1, indent + 2);
lines.push(format!("{pi}}}"));
lines.push(format!("{p}}}"));
}
RsTypeKind::Array(arr) => {
let idx = format!("_i{depth}");
let pi = pad(indent + 1);
lines.push(format!("{p}for (int {idx} = 0; {idx} < {len}; {idx}++)", len = arr.len));
lines.push(format!("{p}{{"));
self.emit_serialize(lines, arr.ty, &format!("{val}[{idx}]"), depth + 1, indent + 1);
lines.push(format!("{p}}}"));
}
RsTypeKind::Enum(_) => {
lines.push(format!("{p}writer.Write({val}.ToUnmanaged()._variant);"));
}
RsTypeKind::Struct(s) => {
for f in &s.fields {
self.emit_serialize(lines, f.ty, &format!("{val}.{}", f.name), depth, indent);
}
}
_ => {
lines.push(format!("{p}/* unsupported wire type for {val} */"));
}
}
}
pub fn emit_deserialize(&self, lines: &mut Vec<String>, ty_id: TypeId, target: &str, depth: usize, indent: usize) {
let Some(ty) = self.rs_types.get(&ty_id) else { return };
let p = pad(indent);
match &ty.kind {
RsTypeKind::Primitive(prim) => {
lines.push(format!("{p}{target} = {};", cs_read_primitive(*prim)));
}
RsTypeKind::WireOnly(WireOnly::String) => {
lines.push(format!(
"{p}{{ var _len = reader.ReadUInt32(); {target} = _len > 0 ? System.Text.Encoding.UTF8.GetString(reader.ReadBytes((int)_len)) : \"\"; }}"
));
}
RsTypeKind::WireOnly(WireOnly::Vec(inner_id)) => {
let cs_inner = self.cs_type_name(*inner_id);
let count = format!("_count{depth}");
let idx = format!("_i{depth}");
let elem_var = format!("_elem{depth}");
let pi = pad(indent + 1);
lines.push(format!("{p}{{"));
lines.push(format!("{pi}var {count} = reader.ReadUInt32();"));
lines.push(format!("{pi}{target} = new List<{cs_inner}>((int){count});"));
lines.push(format!("{pi}for (uint {idx} = 0; {idx} < {count}; {idx}++)"));
lines.push(format!("{pi}{{"));
lines.push(format!("{pi} {cs_inner} {elem_var} = default;"));
self.emit_deserialize(lines, *inner_id, &elem_var, depth + 1, indent + 2);
lines.push(format!("{pi} {target}.Add({elem_var});"));
lines.push(format!("{pi}}}"));
lines.push(format!("{p}}}"));
}
RsTypeKind::WireOnly(WireOnly::Map(k_id, v_id)) => {
let cs_k = self.cs_type_name(*k_id);
let cs_v = self.cs_type_name(*v_id);
let count = format!("_count{depth}");
let idx = format!("_i{depth}");
let k_var = format!("_key{depth}");
let v_var = format!("_val{depth}");
let pi = pad(indent + 1);
let pi2 = pad(indent + 2);
lines.push(format!("{p}{{"));
lines.push(format!("{pi}var {count} = reader.ReadUInt32();"));
lines.push(format!("{pi}{target} = new Dictionary<{cs_k}, {cs_v}>((int){count});"));
lines.push(format!("{pi}for (uint {idx} = 0; {idx} < {count}; {idx}++)"));
lines.push(format!("{pi}{{"));
lines.push(format!("{pi2}{cs_k} {k_var} = default;"));
self.emit_deserialize(lines, *k_id, &k_var, depth + 1, indent + 2);
lines.push(format!("{pi2}{cs_v} {v_var} = default;"));
self.emit_deserialize(lines, *v_id, &v_var, depth + 1, indent + 2);
lines.push(format!("{pi2}{target}[{k_var}] = {v_var};"));
lines.push(format!("{pi}}}"));
lines.push(format!("{p}}}"));
}
RsTypeKind::Array(arr) => {
let cs_elem = self.cs_type_name(arr.ty);
let idx = format!("_i{depth}");
let pi = pad(indent + 1);
lines.push(format!("{p}{target} = new {cs_elem}[{len}];", len = arr.len));
lines.push(format!("{p}for (int {idx} = 0; {idx} < {len}; {idx}++)", len = arr.len));
lines.push(format!("{p}{{"));
self.emit_deserialize(lines, arr.ty, &format!("{target}[{idx}]"), depth + 1, indent + 1);
lines.push(format!("{p}}}"));
}
RsTypeKind::Enum(_) => {
let enum_name = self.cs_type_name(ty_id);
lines.push(format!("{p}{{ var _u = new {enum_name}.Unmanaged(); _u._variant = reader.ReadInt32(); {target} = _u.ToManaged(); }}"));
}
RsTypeKind::Struct(s) => {
let struct_name = self.cs_type_name(ty_id);
lines.push(format!("{p}{target} = new {struct_name}();"));
for f in &s.fields {
self.emit_deserialize(lines, f.ty, &format!("{target}.{}", f.name), depth, indent);
}
}
_ => {
lines.push(format!("{p}/* unsupported wire type for {target} */"));
}
}
}
pub fn emit_size(&self, lines: &mut Vec<String>, ty_id: TypeId, val: &str, depth: usize, indent: usize) {
let Some(ty) = self.rs_types.get(&ty_id) else { return };
let p = pad(indent);
match &ty.kind {
RsTypeKind::Primitive(prim) => {
lines.push(format!("{p}_size += {};", cs_primitive_size(*prim)));
}
RsTypeKind::WireOnly(WireOnly::String) => {
lines.push(format!("{p}_size += 4 + System.Text.Encoding.UTF8.GetByteCount({val} ?? \"\");"));
}
RsTypeKind::WireOnly(WireOnly::Vec(inner_id)) => {
let iter = format!("_item{depth}");
let pi = pad(indent + 1);
lines.push(format!("{p}_size += 4;"));
lines.push(format!("{p}if ({val} != null)"));
lines.push(format!("{p}{{"));
lines.push(format!("{pi}foreach (var {iter} in {val})"));
lines.push(format!("{pi}{{"));
self.emit_size(lines, *inner_id, &iter, depth + 1, indent + 2);
lines.push(format!("{pi}}}"));
lines.push(format!("{p}}}"));
}
RsTypeKind::WireOnly(WireOnly::Map(k_id, v_id)) => {
let kv = format!("_kv{depth}");
let pi = pad(indent + 1);
lines.push(format!("{p}_size += 4;"));
lines.push(format!("{p}if ({val} != null)"));
lines.push(format!("{p}{{"));
lines.push(format!("{pi}foreach (var {kv} in {val})"));
lines.push(format!("{pi}{{"));
self.emit_size(lines, *k_id, &format!("{kv}.Key"), depth + 1, indent + 2);
self.emit_size(lines, *v_id, &format!("{kv}.Value"), depth + 1, indent + 2);
lines.push(format!("{pi}}}"));
lines.push(format!("{p}}}"));
}
RsTypeKind::Array(arr) => {
let idx = format!("_i{depth}");
let pi = pad(indent + 1);
lines.push(format!("{p}for (int {idx} = 0; {idx} < {len}; {idx}++)", len = arr.len));
lines.push(format!("{p}{{"));
self.emit_size(lines, arr.ty, &format!("{val}[{idx}]"), depth + 1, indent + 1);
lines.push(format!("{p}}}"));
}
RsTypeKind::Enum(e) => {
let prim = enum_repr_primitive(e);
lines.push(format!("{p}_size += {};", cs_primitive_size(prim)));
}
RsTypeKind::Struct(s) => {
for f in &s.fields {
self.emit_size(lines, f.ty, &format!("{val}.{}", f.name), depth, indent);
}
}
_ => {}
}
}
}
fn enum_repr_primitive(e: &interoptopus::lang::types::Enum) -> Primitive {
match e.repr.layout {
Layout::Primitive(p) => p,
_ => Primitive::U32,
}
}
fn pad(indent: usize) -> String {
" ".repeat(indent)
}
fn cs_primitive_name(p: Primitive) -> &'static str {
match p {
Primitive::Void => "void",
Primitive::Bool => "bool",
Primitive::U8 => "byte",
Primitive::U16 => "ushort",
Primitive::U32 => "uint",
Primitive::U64 => "ulong",
Primitive::I8 => "sbyte",
Primitive::I16 => "short",
Primitive::I32 => "int",
Primitive::I64 => "long",
Primitive::F32 => "float",
Primitive::F64 => "double",
Primitive::Usize | Primitive::Isize => "long",
}
}
fn cs_read_primitive(p: Primitive) -> &'static str {
match p {
Primitive::Bool => "reader.ReadByte() != 0",
Primitive::U8 => "reader.ReadByte()",
Primitive::U16 => "reader.ReadUInt16()",
Primitive::U32 => "reader.ReadUInt32()",
Primitive::U64 => "reader.ReadUInt64()",
Primitive::I8 => "reader.ReadSByte()",
Primitive::I16 => "reader.ReadInt16()",
Primitive::I32 => "reader.ReadInt32()",
Primitive::I64 => "reader.ReadInt64()",
Primitive::F32 => "reader.ReadSingle()",
Primitive::F64 => "reader.ReadDouble()",
Primitive::Usize | Primitive::Isize => "reader.ReadInt64()",
Primitive::Void => "default",
}
}
fn cs_primitive_size(p: Primitive) -> &'static str {
match p {
Primitive::Void => "0",
Primitive::Bool | Primitive::U8 | Primitive::I8 => "1",
Primitive::U16 | Primitive::I16 => "2",
Primitive::U32 | Primitive::I32 | Primitive::F32 => "4",
Primitive::U64 | Primitive::I64 | Primitive::F64 | Primitive::Usize | Primitive::Isize => "8",
}
}