【特性】新增生成类型 cfg code_rust_json(多态支持有一些问题)

main
walon 2021-09-27 12:52:09 +08:00
parent dad7fa0ea8
commit c29c00bbe3
22 changed files with 685 additions and 4 deletions

View File

@ -17,7 +17,7 @@
## 介绍
目前已经存在很多导表工具如tabtoy、xls2json它们功能多为excel文件到其他格式的转换工具及简单代码生成器,勉强满足中小类型项目的需求。
目前存在的配置工具它们功能多为excel文件到json之类格式的转换工具及简单代码生成器,勉强满足中小类型项目的需求。
在中大型游戏项目中基本都会有技能、行为树之类的复杂功能。这些功能有非常复杂的数据结构往往使用自定义编辑器制作并以json、xml等文件格式保存。就算常规的excel表也经常出现复杂的数据结构需求。这些简单工具面对此类需求要么无法支持要么就强迫策划和程序使用拆表等奇技淫巧严重影响开发效率。
luban相较于常规的excel导表工具有以下核心优势
@ -77,6 +77,7 @@ luban相较于常规的excel导表工具有以下核心优势
- js 和 typescript (3.0+)
- python (3.0+)
- erlang (18+)
- rust (1.5+)
- 支持主流引擎和平台
- unity + c#
- unity + [tolua](https://github.com/topameng/tolua)、[xlua](https://github.com/Tencent/xLua)
@ -96,8 +97,6 @@ luban相较于常规的excel导表工具有以下核心优势
- 其他所有支持lua的引擎和平台
- 其他所有支持js的引擎和平台
-----
## 快速上手
以创建一个道具表为例

View File

@ -121,6 +121,11 @@ namespace Luban.Common.Utils
return module.Replace('.', '_') + "_" + name;
}
public static string MakeRustFullName(string module, string name)
{
return MakeGoNamespace(module) + "_" + name;
}
public static string MakeNamespace(string module, string subModule)
{
if (module.Length == 0)

View File

@ -20,7 +20,7 @@ namespace Luban.Job.Cfg
[Option("output_data_json_monolithic_file", Required = false, HelpText = "output monolithic json file")]
public string OutputDataJsonMonolithicFile { get; set; }
[Option("gen_types", Required = true, HelpText = "code_cs_bin,code_cs_json,code_cs_unity_json,code_lua_bin,code_java_bin,code_java_json,code_go_bin,code_go_json,code_cpp_bin,code_python3_json,code_typescript_bin,code_typescript_json,data_bin,data_lua,data_json,data_json2,data_json_monolithic,data_resources,data_template . can be multi")]
[Option("gen_types", Required = true, HelpText = "code_cs_bin,code_cs_json,code_cs_unity_json,code_lua_bin,code_java_bin,code_java_json,code_go_bin,code_go_json,code_cpp_bin,code_python3_json,code_typescript_bin,code_typescript_json,code_rust_json,data_bin,data_lua,data_json,data_json2,data_json_monolithic,data_resources,data_template . can be multi")]
public string GenType { get; set; }
[Option("template_name", Required = false, HelpText = "template name. use with gen_types=data_template")]

View File

@ -0,0 +1,70 @@
using Luban.Job.Cfg.Defs;
using Luban.Job.Common.Defs;
using Luban.Job.Common.Utils;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Luban.Job.Cfg.Generate
{
[Render("code_rust_json")]
class RustCodeJsonRender : CodeRenderBase
{
public override void Render(GenContext ctx)
{
string genType = ctx.GenType;
var args = ctx.GenArgs;
ctx.Render = this;
ctx.Lan = RenderFileUtil.GetLanguage(genType);
var lines = new List<string>();
GenerateCodeMonolithic(ctx, "mod.rs", lines, ls =>
{
var template = StringTemplateUtil.GetTemplate("config/rust_json/mod_header");
var result = template.RenderCode(ctx.ExportTypes);
ls.Add(result);
}, null);
}
public override string Render(DefConst c)
{
return RenderUtil.RenderRustConstClass(c);
}
public override string Render(DefEnum e)
{
return RenderUtil.RenderRustEnumClass(e);
}
public override string Render(DefBean b)
{
var template = StringTemplateUtil.GetTemplate("config/rust_json/bean");
var result = template.RenderCode(b);
return result;
}
public override string Render(DefTable p)
{
var template = StringTemplateUtil.GetTemplate("config/rust_json/table");
var result = template.RenderCode(p);
return result;
}
public override string RenderService(string name, string module, List<DefTable> tables)
{
var template = StringTemplateUtil.GetTemplate("config/rust_json/tables");
var result = template.RenderCode(new
{
Name = name,
Namespace = module,
Tables = tables,
});
return result;
}
}
}

View File

@ -0,0 +1,27 @@
using Luban.Job.Common.Types;
using Luban.Job.Common.TypeVisitors;
namespace Luban.Job.Cfg.TypeVisitors
{
class RustJsonConstructorVisitor : DecoratorFuncVisitor<string, string>
{
public static RustJsonConstructorVisitor Ins { get; } = new();
public override string DoAccept(TType type, string jsonFieldName)
{
if (type.IsNullable)
{
return $"if !{jsonFieldName}.is_null() {{ Some({type.Apply(RustJsonUnderingConstructorVisitor.Ins, jsonFieldName)}) }} else {{ None }}";
}
else
{
return type.Apply(RustJsonUnderingConstructorVisitor.Ins, jsonFieldName);
}
}
//public override string Accept(TBean type, string bytebufName, string fieldName)
//{
// return type.Apply(TypescriptJsonUnderingConstructorVisitor.Ins, bytebufName, fieldName);
//}
}
}

View File

@ -0,0 +1,131 @@
using Luban.Job.Cfg.Datas;
using Luban.Job.Common.Types;
using Luban.Job.Common.TypeVisitors;
namespace Luban.Job.Cfg.TypeVisitors
{
class RustJsonUnderingConstructorVisitor : ITypeFuncVisitor<string, string>
{
public static RustJsonUnderingConstructorVisitor Ins { get; } = new();
private static string AsType(string jsonVarName, string rawType)
{
return $"match {jsonVarName}.as_{rawType}() {{ Some(__x__) => __x__, None => return Err(LoadError{{}}) }}";
}
public string Accept(TBool type, string jsonVarName)
{
return AsType(jsonVarName, "bool");
}
public string Accept(TByte type, string jsonVarName)
{
return AsType(jsonVarName, "u8");
}
public string Accept(TShort type, string jsonVarName)
{
return AsType(jsonVarName, "i16");
}
public string Accept(TFshort type, string jsonVarName)
{
return AsType(jsonVarName, "i16");
}
public string Accept(TInt type, string jsonVarName)
{
return AsType(jsonVarName, "i32");
}
public string Accept(TFint type, string jsonVarName)
{
return AsType(jsonVarName, "i32");
}
public string Accept(TLong type, string jsonVarName)
{
return AsType(jsonVarName, "i64");
}
public string Accept(TFlong type, string jsonVarName)
{
return AsType(jsonVarName, "i64");
}
public string Accept(TFloat type, string jsonVarName)
{
return AsType(jsonVarName, "f32");
}
public string Accept(TDouble type, string jsonVarName)
{
return AsType(jsonVarName, "f64");
}
public string Accept(TEnum type, string jsonVarName)
{
return AsType(jsonVarName, "i32");
}
public string Accept(TString type, string jsonVarName)
{
return $"match {jsonVarName}.as_str() {{ Some(__x__) => __x__.to_string(), None => return Err(LoadError{{}}) }}";
}
public string Accept(TBytes type, string jsonVarName)
{
throw new System.NotSupportedException();
}
public string Accept(TText type, string jsonVarName)
{
return $"{{ if !{jsonVarName}[\"{DText.KEY_NAME}\"].is_string() {{ return Err(LoadError{{}}); }} match {jsonVarName}[\"{DText.TEXT_NAME}\"].as_str() {{ Some(__x__) => __x__.to_string(), None => return Err(LoadError{{}}) }} }}";
}
public string Accept(TBean type, string jsonVarName)
{
return $"{type.Bean.RustFullName}::new(&{jsonVarName})?";
}
public string Accept(TArray type, string jsonVarName)
{
return $"{{ if !{jsonVarName}.is_array() {{ return Err(LoadError{{}}); }} let mut __list__ = vec![]; for __e in {jsonVarName}.members() {{ __list__.push({type.ElementType.Apply(this, "__e")}); }} __list__}}";
}
public string Accept(TList type, string jsonVarName)
{
return $"{{ if !{jsonVarName}.is_array() {{ return Err(LoadError{{}}); }} let mut __list__ = vec![]; for __e in {jsonVarName}.members() {{ __list__.push({type.ElementType.Apply(this, "__e")}); }} __list__}}";
}
public string Accept(TSet type, string jsonVarName)
{
return $"{{ if !{jsonVarName}.is_array() {{ return Err(LoadError{{}}); }} let mut __set__ = std::collections::HashSet::new(); for __e in {jsonVarName}.members() {{ __set__.insert({type.ElementType.Apply(this, "__e")}); }} __set__}}";
}
public string Accept(TMap type, string jsonVarName)
{
return $"{{ if !{jsonVarName}.is_array() {{ return Err(LoadError{{}}); }} let mut __map__ = std::collections::HashMap::new(); for __e in {jsonVarName}.members() {{ __map__.insert({type.KeyType.Apply(this, "__e[0]")}, {type.ValueType.Apply(this, "__e[1]")}); }} __map__}}";
}
public string Accept(TVector2 type, string jsonVarName)
{
return $"Vector2::new(&{jsonVarName})?";
}
public string Accept(TVector3 type, string jsonVarName)
{
return $"Vector3::new(&{jsonVarName})?";
}
public string Accept(TVector4 type, string jsonVarName)
{
return $"Vector4::new(&{jsonVarName})?";
}
public string Accept(TDateTime type, string jsonVarName)
{
return AsType(jsonVarName, "i32");
}
}
}

View File

@ -217,6 +217,11 @@ namespace Luban.Job.Cfg.Utils
}
}
public static string RustJsonConstructor(string jsonFieldName, TType type)
{
return type.Apply(RustJsonConstructorVisitor.Ins, jsonFieldName);
}
//public static string DeserializeTextKeyField(DefField field, string lan, string bufName)
//{
// switch (lan)

View File

@ -60,6 +60,8 @@ namespace Luban.Job.Common.Defs
public string GoStyleName => CsStyleName;
public string RustStyleName => Name != "type" ? Name : "r#" + Name;
//public string GoStyleAssignName => CType.IsNullable ? "*" + CsStyleName : CsStyleName;
public string Type { get; }

View File

@ -44,6 +44,8 @@ namespace Luban.Job.Common.Defs
public string PyFullName => TypeUtil.MakePyFullName(Namespace, Name);
public string RustFullName => TypeUtil.MakeRustFullName(Namespace, Name);
public string Comment { get; protected set; }
public Dictionary<string, string> Tags { get; protected set; }

View File

@ -163,6 +163,23 @@ namespace Luban.Job.Common.Defs
return type.Apply(LuaConstValueVisitor.Ins, value);
}
public static string RustClassName(TBean type)
{
return type.Bean.RustFullName;
}
public static string RustDefineType(TType type)
{
return type.Apply(RustTypeNameVisitor.Ins);
}
public static string RustConstValue(TType type, string value)
{
return type.Apply(LuaConstValueVisitor.Ins, value);
}
public static bool HasTag(dynamic obj, string attrName)
{
return obj.HasTag(attrName);

View File

@ -11,5 +11,6 @@ namespace Luban.Job.Common
TYPESCRIPT,
PYTHON,
ERLANG,
RUST,
}
}

View File

@ -0,0 +1,35 @@
using Luban.Job.Common.Types;
namespace Luban.Job.Common.TypeVisitors
{
public class RustTypeNameVisitor : DecoratorFuncVisitor<string>
{
public static RustTypeNameVisitor Ins { get; } = new();
public override string DoAccept(TType type)
{
if (type.IsNullable)
{
if (type.IsBean)
{
return $"std::option::Option<std::rc::Rc<{type.Apply(RustTypeUnderlyingNameVisitor.Ins)}>>";
}
else
{
return $"std::option::Option<{type.Apply(RustTypeUnderlyingNameVisitor.Ins)}>";
}
}
else
{
if (type.IsBean)
{
return $"std::rc::Rc<{type.Apply(RustTypeUnderlyingNameVisitor.Ins)}>";
}
else
{
return type.Apply(RustTypeUnderlyingNameVisitor.Ins);
}
}
}
}
}

View File

@ -0,0 +1,124 @@
using Luban.Job.Common.Types;
namespace Luban.Job.Common.TypeVisitors
{
public class RustTypeUnderlyingNameVisitor : ITypeFuncVisitor<string>
{
public static RustTypeUnderlyingNameVisitor Ins { get; } = new();
public string Accept(TBool type)
{
return "bool";
}
public string Accept(TByte type)
{
return "u8";
}
public string Accept(TShort type)
{
return "i16";
}
public string Accept(TFshort type)
{
return "i16";
}
public string Accept(TInt type)
{
return "i32";
}
public string Accept(TFint type)
{
return "i32";
}
public string Accept(TLong type)
{
return "i64";
}
public string Accept(TFlong type)
{
return "i64";
}
public string Accept(TFloat type)
{
return "f32";
}
public string Accept(TDouble type)
{
return "f64";
}
public string Accept(TEnum type)
{
return "i32";
}
public string Accept(TString type)
{
return "String";
}
public string Accept(TBytes type)
{
throw new System.NotSupportedException();
}
public string Accept(TText type)
{
return "String";
}
public string Accept(TBean type)
{
return type.Bean.RustFullName;
}
public string Accept(TArray type)
{
return $"Vec<{type.ElementType.Apply(RustTypeNameVisitor.Ins)}>";
}
public string Accept(TList type)
{
return $"Vec<{type.ElementType.Apply(RustTypeNameVisitor.Ins)}>";
}
public string Accept(TSet type)
{
return $"std::collections::HashSet<{type.ElementType.Apply(RustTypeNameVisitor.Ins)}>";
}
public string Accept(TMap type)
{
return $"std::collections::HashMap<{type.KeyType.Apply(RustTypeNameVisitor.Ins)}, {type.ValueType.Apply(RustTypeNameVisitor.Ins)}>";
}
public string Accept(TVector2 type)
{
return "Vector2";
}
public string Accept(TVector3 type)
{
return "Vector3";
}
public string Accept(TVector4 type)
{
return "Vector4";
}
public string Accept(TDateTime type)
{
return "i32";
}
}
}

View File

@ -17,6 +17,7 @@ namespace Luban.Job.Common.Utils
case ELanguage.LUA: return fullName.Replace('.', '_') + ".lua";
case ELanguage.JS: return fullName + ".js";
case ELanguage.TYPESCRIPT: return fullName.Replace('.', '/') + ".ts";
case ELanguage.RUST: return fullName.Replace('.', '_') + ".rs";
default: throw new NotSupportedException();
}
@ -97,6 +98,7 @@ namespace Luban.Job.Common.Utils
{ "typescript", ELanguage.TYPESCRIPT },
{ "javascript", ELanguage.JS },
{ "erlang", ELanguage.ERLANG },
{ "rust", ELanguage.RUST },
};
public static ELanguage GetLanguage(string genType)

View File

@ -118,6 +118,26 @@ namespace Luban.Job.Common.Utils
{
var template = StringTemplateUtil.GetTemplate("common/typescript/enum");
var result = template.Render(e);
return result;
}
public static string RenderRustConstClass(DefConst c)
{
var ctx = new TemplateContext();
var env = new TTypeTemplateCommonExtends
{
["x"] = c
};
ctx.PushGlobal(env);
var template = StringTemplateUtil.GetTemplate("common/rust/const");
var result = template.Render(ctx);
return result;
}
public static string RenderRustEnumClass(DefEnum e)
{
var template = StringTemplateUtil.GetTemplate("common/rust/enum");
var result = template.Render(e);
return result;
}

View File

@ -67,6 +67,12 @@
<None Update="Templates\common\python\enum.tpl">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Templates\common\rust\const.tpl">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Templates\common\rust\enum.tpl">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Templates\common\typescript\const.tpl">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
@ -175,6 +181,18 @@
<None Update="Templates\config\python_json\tables.tpl">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Templates\config\rust_json\bean.tpl">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Templates\config\rust_json\mod_header.tpl">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Templates\config\rust_json\table.tpl">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Templates\config\rust_json\tables.tpl">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Templates\config\typescript_bin\bean.tpl">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>

View File

@ -0,0 +1,18 @@
{{~if x.comment != '' ~}}
/**
* {{x.comment}}
*/
{{~end~}}
#[allow(non_snake_case)]
pub mod {{x.rust_full_name}} {
{{~ for item in x.items ~}}
{{~if item.comment != '' ~}}
/**
* {{item.comment}}
*/
{{~end~}}
#[allow(dead_code)]
pub const {{string.upcase item.name}}: {{rust_define_type item.ctype}} = {{rust_const_value item.ctype item.value}};
{{~end~}}
}

View File

@ -0,0 +1,18 @@
{{~if comment != '' ~}}
/**
* {{comment}}
*/
{{~end~}}
#[allow(dead_code)]
#[allow(non_camel_case_types)]
pub enum {{rust_full_name}} {
{{~for item in items ~}}
{{~if item.comment != '' ~}}
/**
* {{item.comment}}
*/
{{~end~}}
{{item.name}} = {{item.int_value}},
{{~end~}}
}

View File

@ -0,0 +1,31 @@
{{
name = x.rust_full_name
parent_def_type = x.parent_def_type
export_fields = x.export_fields
hierarchy_export_fields = x.hierarchy_export_fields
}}
{{~if x.comment != '' ~}}
/**
* {{x.comment}}
*/
{{~end~}}
#[allow(non_camel_case_types)]
pub struct {{name}} {
{{~for field in hierarchy_export_fields~}}
pub {{field.rust_style_name}}: {{rust_define_type field.ctype}},
{{~end~}}
}
impl {{name}} {
#[allow(dead_code)]
pub fn new(__js: &json::JsonValue) -> Result<std::rc::Rc<{{name}}>, LoadError> {
let __b = {{name}} {
{{~for field in hierarchy_export_fields~}}
{{field.rust_style_name}}: {{rust_json_constructor ('__js["' + field.name + '"]') field.ctype}},
{{~end~}}
};
Ok(std::rc::Rc::new(__b))
}
}

View File

@ -0,0 +1,59 @@
pub struct LoadError {
}
impl std::fmt::Debug for LoadError {
fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { Ok(()) }
}
#[allow(dead_code)]
pub struct Vector2 {
pub x:f32,
pub y:f32,
}
impl Vector2 {
pub fn new(__js:&json::JsonValue) -> Result<Vector2, LoadError> {
Ok(Vector2{
x: match __js["x"].as_f32() { Some(__x__) => __x__, None => return Err(LoadError{})},
y: match __js["y"].as_f32() { Some(__x__) => __x__, None => return Err(LoadError{})},
})
}
}
#[allow(dead_code)]
pub struct Vector3 {
pub x:f32,
pub y:f32,
pub z:f32,
}
impl Vector3 {
pub fn new(__js:&json::JsonValue) -> Result<Vector3, LoadError> {
Ok(Vector3{
x: match __js["x"].as_f32() { Some(__x__) => __x__, None => return Err(LoadError{})},
y: match __js["y"].as_f32() { Some(__x__) => __x__, None => return Err(LoadError{})},
z: match __js["z"].as_f32() { Some(__x__) => __x__, None => return Err(LoadError{})},
})
}
}
#[allow(dead_code)]
pub struct Vector4 {
pub x:f32,
pub y:f32,
pub z:f32,
pub w:f32,
}
impl Vector4 {
pub fn new(__js:&json::JsonValue) -> Result<Vector4, LoadError> {
Ok(Vector4{
x: match __js["x"].as_f32() { Some(__x__) => __x__, None => return Err(LoadError{})},
y: match __js["y"].as_f32() { Some(__x__) => __x__, None => return Err(LoadError{})},
z: match __js["z"].as_f32() { Some(__x__) => __x__, None => return Err(LoadError{})},
w: match __js["w"].as_f32() { Some(__x__) => __x__, None => return Err(LoadError{})},
})
}
}

View File

@ -0,0 +1,65 @@
{{
name = x.rust_full_name
key_type = x.key_ttype
value_type = x.value_ttype
}}
{{~if x.comment != '' ~}}
/**
* {{x.comment}}
*/
{{~end~}}
#[allow(non_camel_case_types)]
pub struct {{name}} {
{{~if x.is_map_table ~}}
data_list: Vec<{{rust_define_type value_type}}>,
data_map: std::collections::HashMap<{{rust_define_type key_type}}, {{rust_define_type value_type}}>,
{{~else~}}
data: {{rust_define_type value_type}},
{{~end~}}
}
impl {{name}}{
pub fn new(__js: &json::JsonValue) -> Result<std::rc::Rc<{{name}}>, LoadError> {
{{~if x.is_map_table ~}}
if !__js.is_array() {
return Err(LoadError{});
}
let mut t = {{name}} {
data_list : Vec::new(),
data_map: std::collections::HashMap::new(),
};
for __e in __js.members() {
let __v = match {{rust_class_name value_type}}::new(__e) {
Ok(x) => x,
Err(err) => return Err(err),
};
let __v2 = std::rc::Rc::clone(&__v);
t.data_list.push(__v);
t.data_map.insert(__v2.{{x.index_field.rust_style_name}}.clone(), __v2);
}
Ok(std::rc::Rc::new(t))
}
#[allow(dead_code)]
pub fn get_data_map(self:&{{name}}) -> &std::collections::HashMap<{{rust_define_type key_type}}, {{rust_define_type value_type}}> { &self.data_map }
#[allow(dead_code)]
pub fn get_data_list(self:&{{name}}) -> &Vec<{{rust_define_type value_type}}> { &self.data_list }
#[allow(dead_code)]
pub fn get(self:&{{name}}, key: {{rust_define_type key_type}}) -> std::option::Option<&{{rust_define_type value_type}}> { self.data_map.get(&key) }
{{~else~}}
if !__js.is_array() || __js.len() != 1 {
return Err(LoadError{});
}
let __v = match {{rust_class_name value_type}}::new(&__js[0]) {
Ok(x) => x,
Err(err) => return Err(err),
};
let t = {{name}} {
data: __v,
};
Ok(std::rc::Rc::new(t))
}
#[allow(dead_code)]
pub fn get_data(self:&{{name}}) -> &{{rust_define_type value_type}} { &self.data }
{{~end~}}
}

View File

@ -0,0 +1,32 @@
{{
name = x.name
namespace = x.namespace
tables = x.tables
}}
type JsonLoader = fn(&str) -> Result<json::JsonValue, LoadError>;
#[allow(non_camel_case_types)]
pub struct {{name}} {
{{~ for table in tables ~}}
{{~if table.comment != '' ~}}
/**
* {{table.comment}}
*/
{{~end~}}
pub {{string.downcase table.name}}: std::rc::Rc<{{table.rust_full_name}}>,
{{~end~}}
}
impl {{name}} {
#[allow(dead_code)]
pub fn new(loader: JsonLoader) -> std::result::Result<std::rc::Rc<Tables>, LoadError> {
let tables = Tables {
{{~for table in tables ~}}
{{string.downcase table.name}}: {{table.rust_full_name}}::new(&loader("{{table.output_data_file}}")?)?,
{{~end~}}
};
return Ok(std::rc::Rc::new(tables));
}
}