Add packet struct write

This commit is contained in:
Vladislavs Golubs 2021-02-14 17:24:10 +03:00
parent e7eace3ef5
commit 5a2b7e81fa
8 changed files with 306 additions and 0 deletions

View File

@ -3,4 +3,5 @@
members = [
"protocol",
"protocol-derive",
"protocol-generator",
]

View File

@ -0,0 +1,17 @@
[package]
name = "minecraft-protocol-generator"
version = "0.0.0"
authors = ["vagola <vladislavs.golubs@yandex.ru>"]
edition = "2018"
description = "Minecraft protocol generator"
license = "MIT"
homepage = "https://github.com/eihwaz/minecraft-protocol"
repository = "https://github.com/eihwaz/minecraft-protocol"
keywords = ["minecraft", "protocol", "packet", "io"]
[dependencies]
serde = "1.0.120"
serde_json = "1.0"
handlebars = "3.5.2"
heck = "0.3.2"
protodef-parser = "0.1.0"

View File

@ -0,0 +1,12 @@
use handlebars::RenderError;
#[derive(Debug)]
pub enum FrontendError {
TemplateRenderError { render_error: RenderError },
}
impl From<RenderError> for FrontendError {
fn from(render_error: RenderError) -> Self {
FrontendError::TemplateRenderError { render_error }
}
}

View File

@ -0,0 +1,138 @@
use crate::error::FrontendError;
use handlebars::Handlebars;
use serde::Serialize;
use std::io::Write;
#[derive(Debug, Eq, PartialEq, Serialize)]
#[serde(tag = "type")]
pub enum DataType {
#[serde(rename(serialize = "bool"))]
Boolean,
#[serde(rename(serialize = "i8"))]
Byte,
#[serde(rename(serialize = "u8"))]
UnsignedByte,
#[serde(rename(serialize = "i16"))]
Short,
#[serde(rename(serialize = "u16"))]
UnsignedShort,
#[serde(rename(serialize = "i32"))]
Int {
var_int: bool,
},
#[serde(rename(serialize = "u32"))]
UnsignedInt,
#[serde(rename(serialize = "i64"))]
Long {
var_long: bool,
},
#[serde(rename(serialize = "u64"))]
UnsignedLong,
#[serde(rename(serialize = "f32"))]
Float,
#[serde(rename(serialize = "f64"))]
Double,
String {
max_length: u16,
},
Uuid {
hyphenated: bool,
},
#[serde(rename(serialize = "Vec<u8>"))]
ByteArray {
rest: bool,
},
CompoundTag,
RefType {
ref_name: String,
},
}
#[derive(Debug, Eq, PartialEq, Serialize)]
pub struct PacketStruct {
pub name: String,
pub fields: Vec<Field>,
}
impl PacketStruct {
pub fn new(name: impl ToString, fields: Vec<Field>) -> PacketStruct {
PacketStruct {
name: name.to_string(),
fields,
}
}
}
#[derive(Debug, Eq, PartialEq, Serialize)]
pub struct Field {
pub name: String,
#[serde(flatten)]
pub data_type: DataType,
}
impl Field {
pub fn new(name: impl ToString, data_type: DataType) -> Field {
Field {
name: name.to_string(),
data_type,
}
}
}
fn write_packet_struct<W: Write>(
template_engine: &Handlebars,
packet_struct: PacketStruct,
write: &mut W,
) -> Result<(), FrontendError> {
template_engine.render_to_write("packet_struct", &packet_struct, write)?;
Ok(())
}
#[cfg(test)]
mod tests {
use crate::frontend::{write_packet_struct, DataType, Field, PacketStruct};
use crate::templates;
#[test]
fn test_write_packet_struct() {
let template_engine = templates::create_template_engine("templates");
let fields = vec![
Field::new("boolean", DataType::Boolean),
Field::new("byte", DataType::Byte),
Field::new("unsigned_byte", DataType::UnsignedByte),
Field::new("short", DataType::Short),
Field::new("unsigned_short", DataType::UnsignedShort),
Field::new("int", DataType::Int { var_int: false }),
Field::new("varint", DataType::Int { var_int: true }),
Field::new("unsigned_int", DataType::UnsignedInt),
Field::new("long", DataType::Long { var_long: false }),
Field::new("varlong", DataType::Long { var_long: true }),
Field::new("unsigned_long", DataType::UnsignedLong),
Field::new("float", DataType::Float),
Field::new("double", DataType::Double),
Field::new("string", DataType::String { max_length: 20 }),
Field::new("uuid", DataType::Uuid { hyphenated: false }),
Field::new("hyphenated", DataType::Uuid { hyphenated: true }),
Field::new("byte_array", DataType::ByteArray { rest: false }),
Field::new("rest", DataType::ByteArray { rest: true }),
Field::new("compound_tag", DataType::CompoundTag),
Field::new(
"ref",
DataType::RefType {
ref_name: "Chat".to_string(),
},
),
];
let packet_struct = PacketStruct::new("TestPacket", fields);
let mut vec = vec![];
write_packet_struct(&template_engine, packet_struct, &mut vec)
.expect("Failed to write packet struct");
let result = String::from_utf8(vec).expect("Failed to convert vec to string");
assert_eq!(result, include_str!("../test/packet_struct.txt"));
}
}

View File

@ -0,0 +1,5 @@
mod error;
mod frontend;
mod templates;
pub fn main() {}

View File

@ -0,0 +1,87 @@
use handlebars::{Context, Handlebars, Helper, Output, RenderContext, RenderError};
use heck::SnakeCase;
pub fn create_template_engine(templates_folder: &str) -> Handlebars<'static> {
let mut template_engine = Handlebars::new();
template_engine.register_helper("snake_case", Box::new(format_snake_case));
template_engine.register_helper("packet_id", Box::new(format_packet_id));
template_engine.register_helper(
"protocol_version_module",
Box::new(format_protocol_version_module),
);
template_engine.register_escape_fn(|s| s.to_owned());
register_template_file(&mut template_engine, templates_folder, "packet_struct");
template_engine
}
fn register_template_file(template_engine: &mut Handlebars, templates_folder: &str, name: &str) {
let tpl_path = format!("{}/{}.hbs", templates_folder, name);
template_engine
.register_template_file(name, tpl_path)
.expect("Failed to register template");
}
fn format_snake_case(
h: &Helper,
_: &Handlebars,
_: &Context,
_: &mut RenderContext,
out: &mut dyn Output,
) -> Result<(), RenderError> {
let str = h
.param(0)
.and_then(|v| v.value().as_str())
.ok_or(RenderError::new(
"Param 0 with str type is required for snake case helper.",
))? as &str;
let snake_case_str = str.to_snake_case();
out.write(snake_case_str.as_ref())?;
Ok(())
}
fn format_packet_id(
h: &Helper,
_: &Handlebars,
_: &Context,
_: &mut RenderContext,
out: &mut dyn Output,
) -> Result<(), RenderError> {
let id = h
.param(0)
.and_then(|v| v.value().as_u64())
.ok_or(RenderError::new(
"Param 0 with u64 type is required for packet id helper.",
))? as u64;
let packet_id_str = format!("{:#04X}", id);
out.write(packet_id_str.as_ref())?;
Ok(())
}
fn format_protocol_version_module(
h: &Helper,
_: &Handlebars,
_: &Context,
_: &mut RenderContext,
out: &mut dyn Output,
) -> Result<(), RenderError> {
let version = h
.param(0)
.and_then(|v| v.value().as_str())
.ok_or(RenderError::new(
"Param 0 with str type is required for packet id helper.",
))? as &str;
let formatted_protocol_module_version =
format!("v_{}", version.replace(".", "_").replace("-", "_"));
out.write(formatted_protocol_module_version.as_ref())?;
Ok(())
}

View File

@ -0,0 +1,19 @@
pub struct {{name}} {
{{~#each fields as |f|}}
{{~#if f.var_int}}
#[packet(with = "var_int")]{{/if}}
{{~#if f.var_long}}
#[packet(with = "var_long")]{{/if}}
{{~#if f.rest}}
#[packet(with = "rest")]{{/if}}
{{~#if f.hyphenated}}
#[packet(with = "uuid_hyp_str")]{{/if}}
{{~#if f.max_length}}
#[packet(max_length = {{f.max_length}})]{{/if}}
{{~#if (ne f.type "RefType")}}
pub {{f.name}}: {{f.type}},
{{~else}}
pub {{f.name}}: {{f.ref_name}},
{{~/if}}
{{~/each}}
}

View File

@ -0,0 +1,27 @@
pub struct TestPacket {
pub boolean: bool,
pub byte: i8,
pub unsigned_byte: u8,
pub short: i16,
pub unsigned_short: u16,
pub int: i32,
#[packet(with = "var_int")]
pub varint: i32,
pub unsigned_int: u32,
pub long: i64,
#[packet(with = "var_long")]
pub varlong: i64,
pub unsigned_long: u64,
pub float: f32,
pub double: f64,
#[packet(max_length = 20)]
pub string: String,
pub uuid: Uuid,
#[packet(with = "uuid_hyp_str")]
pub hyphenated: Uuid,
pub byte_array: Vec<u8>,
#[packet(with = "rest")]
pub rest: Vec<u8>,
pub compound_tag: CompoundTag,
pub ref: Chat,
}