Compare commits

..

No commits in common. "master" and "v4.5.1" have entirely different histories.

7 changed files with 66 additions and 304 deletions

View File

@ -1,30 +0,0 @@
name: Publish PHP Package
on:
push:
tags:
- 'v*'
jobs:
docker:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Get tag name
run: echo "TAG=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
- name: zip and send
run: |
ls -la
apt-get update -y
apt-get install -y zip
cd ../
zip -r ${{ gitea.event.repository.name }}.zip ${{ gitea.event.repository.name }} -x '*.git*'
curl -v \
--user composer-pbl:${{ secrets.COMPOSER_PSWD }} \
--upload-file ${{ gitea.event.repository.name }}.zip \
https://git.bit5.ru/api/packages/bit/composer?version=${{ env.TAG }}

View File

@ -1,30 +0,0 @@
## v7.1.0
- Using a shared helper method to streamline the generated code
## v7.0.0
- Using a dedicated wrapper for i18n strings to streamline the generated code
## v6.1.0
- Removed redundant accessor interface declarations in generated slices
## v5.0.0
- Splitting one huge codegenerated file into multiple ones
## v4.10.0
- Added @flt_i18n token to support translations
## v4.9.2
- Added @cs_obsolete token, that mark fields with [Obsolete]. @cs_obsolete:"Comment" will add summary with "Comment" to field
## v4.9.1
- Fixing nested diffable POD structs being incorrectly marked as clean in GetDiff
## v4.8.2
- Fixing thread safety possible issue when reading an array of enums
- Fixing bug when the target enum array property wouldn't be cleared before reading data
## v4.8.0
- Using C# built-in initialization routines instead of MetaIO's methods
## v4.7.0
- Array comparison generated code now takes bitfields token into account for array entries

View File

@ -2,10 +2,12 @@ This package is used for code generation of C# meta structs using Twig templates
Usage example:
$output = \metagen_cs\codegen(null, get_meta(),
[
'namespace' => 'BitGames.Autogen'
]);
foreach($output as $name => $text)
file_put_contents($name, $text);
$twig = \metagen_cs\get_twig();
file_put_contents('bundle.cs',
$twig->render("codegen_bundle.twig",
[
'namespace' => 'BitGames.Autogen',
'meta' => get_meta()
]
)
);

View File

@ -2,37 +2,7 @@
namespace metagen_cs;
use Exception;
function codegen(?string $cache_dir, \mtgMetaInfo $meta, array $options = []) : array
{
$twig = get_twig();
if(!empty($cache_dir))
$twig->setCache($cache_dir);
$twig->addGlobal('meta', $meta);
$options['meta'] = $meta;
if(!isset($options['namespace']))
$options['namespace'] = 'BitGames.Autogen';
$sliced_units = slice_units($meta->getUnits(), 50);
$output = array();
foreach($sliced_units as $slice_idx => $slice_units)
{
$slice_options = $options;
$slice_options['slice_idx'] = $slice_idx;
$slice_options['slice_units'] = $slice_units;
$output['types_'.$slice_idx.'.cs'] = $twig->render('codegen_types_slice.twig', $slice_options);
}
$output['factory.cs'] = $twig->render('codegen_factory.twig', $options);
return $output;
}
function get_twig(array $inc_path = []) : \Twig\Environment
function get_twig(array $inc_path = [])
{
array_unshift($inc_path, __DIR__ . "/../tpl/");
$loader = new \Twig\Loader\FilesystemLoader($inc_path);
@ -49,12 +19,11 @@ function get_twig(array $inc_path = []) : \Twig\Environment
return $twig;
}
function supported_tokens() : array
function supported_tokens()
{
return [
'POD',
'default',
'alias',
'virtual',
'bitfields',
'cloneable',
@ -69,7 +38,6 @@ function supported_tokens() : array
'cs_propget_interface',
'cs_propset_interface',
'cs_propgetset_interface',
'cs_obsolete',
//TODO:
//'i18n'
];
@ -92,7 +60,7 @@ function _add_twig_support(\Twig\Environment $twig)
$twig->addFunction(new \Twig\TwigFunction('has_token',
function($o, $token)
{
return (($o instanceof \mtgMetaUnit) || ($o instanceof \mtgMetaField)) && $o->hasToken($token);
return $o->hasToken($token);
}
));
$twig->addFunction(new \Twig\TwigFunction('has_token_in_parent',
@ -137,18 +105,9 @@ function _add_twig_support(\Twig\Environment $twig)
}
));
$twig->addFilter(new \Twig\TwigFilter('cs_type',
function($type, $fld = null)
{
return cs_type($type, $fld);
}
));
$twig->addFilter(new \Twig\TwigFilter('cs_simple_type',
function($type)
{
if($type instanceof \mtgArrType)
return cs_simple_type($type->getValue());
else
return cs_simple_type($type);
return cs_type($type);
}
));
$twig->addFilter(new \Twig\TwigFilter('cs_type_prefix',
@ -196,7 +155,7 @@ function _add_twig_support(\Twig\Environment $twig)
$twig->addFunction(new \Twig\TwigFunction('get_diff_related_units',
function($o)
{
return get_diff_all_related_structs($o);
return get_all_related_structs($o);
}
));
$twig->addFunction(new \Twig\TwigFunction('get_all_declarable_accessor_interfaces',
@ -213,14 +172,10 @@ function _add_twig_support(\Twig\Environment $twig)
));
}
function cs_type(\mtgType $type, $fld = null)
function cs_type(\mtgType $type)
{
if($type instanceof \mtgBuiltinType || $type instanceof \mtgUserType)
{
if($fld != null && $fld->hasToken("flt_i18n"))
return "MetaI18NString";
return cs_simple_type($type);
}
else if($type instanceof \mtgArrType)
return "List<" . cs_simple_type($type->getValue()) . ">";
else
@ -260,7 +215,7 @@ function cs_simple_type(\mtgType $type)
case "bool":
return "bool";
case "blob":
return "ArraySegment<byte>";
return "byte[]";
}
throw new Exception("Unknown type '{$type}'");
}
@ -371,14 +326,14 @@ function var_reset($name, \mtgType $type, $default = null)
$str .= " = ".trim($default, '"').";";
}
else
$str .= ' = default;';
$str .= ' = null;';
}
else
throw new Exception("Unknown type '$type'");
}
else if($type instanceof \mtgArrType)
{
$str = "if($name == null) $name = new(); else $name.Clear();";
$str = "MetaIO.ClearList(ref $name);";
if($default)
{
@ -418,21 +373,21 @@ function var_reset($name, \mtgType $type, $default = null)
if($is_pod)
$str .= "$name.Reset(); ";
else
$str .= "if($name == null) $name = new(); else $name.Reset(); ";
$str .= "MetaIO.Reset(ref $name); ";
}
if($default)
{
$default_val = is_array($default) ? $default : json_decode($default, true);
if(is_array($default_val))
$default = is_array($default) ? $default : json_decode($default, true);
if(is_array($default))
{
foreach($default_val as $k => $v)
foreach($default as $k => $v)
{
$kf = $type->getField($k);
$str .= var_reset("$name." . $kf->getName(), $kf->getType(), $v);
}
}
else if(is_null_str($default))
else if($default === null)
$str .= "$name = null; ";
else
throw new Exception("Bad default value for struct: " . var_export($default, true));
@ -445,7 +400,7 @@ function var_reset($name, \mtgType $type, $default = null)
function is_null_str($default)
{
return is_string($default) && strtolower($default) === 'null';
return is_string($default) && json_decode($default, true) === null;
}
function var_sync($fname, \mtgType $type, $buf, array $tokens, $opts)
@ -455,10 +410,7 @@ function var_sync($fname, \mtgType $type, $buf, array $tokens, $opts)
$str = '';
if($type instanceof \mtgBuiltinType)
{
if($type->isString() && array_key_exists('flt_i18n', $tokens))
$str .= "$fname.SyncSingular({$buf}, \"{$key_name}\", {$opts});\n"; //TODO: plurals support
else
$str .= "MetaIO.Sync({$buf}, ref {$fname}, \"{$key_name}\", {$opts});\n";
$str .= "MetaIO.Sync({$buf}, ref {$fname}, \"{$key_name}\", {$opts});\n";
}
else if($type instanceof \mtgMetaStruct)
{
@ -471,7 +423,7 @@ function var_sync($fname, \mtgType $type, $buf, array $tokens, $opts)
{
$str .= "int __tmp_{$fname} = (int)$fname;\n";
$str .= "MetaIO.Sync({$buf}, ref __tmp_{$fname}, \"{$key_name}\", {$opts});\n";
$str .= "if($buf.is_read) {$fname} = (".cs_type($type).")__tmp_{$fname};\n";
$str .= "if($buf.is_read) {$fname} = ({$type->getName()})__tmp_{$fname};\n";
}
else if($type instanceof \mtgArrType)
{
@ -481,11 +433,9 @@ function var_sync($fname, \mtgType $type, $buf, array $tokens, $opts)
{
if($type->getValue() instanceof \mtgMetaEnum)
{
$str .= "{\n";
$str .= "var tmp_enums_list = new List<int>(); if(!{$buf}.is_read) { foreach(var _enum_tmp in {$fname}) tmp_enums_list.Add((int)_enum_tmp); }\n";
$str .= "MetaIO.Sync({$buf}, tmp_enums_list, \"{$key_name}\", {$opts});\n";
$str .= "if({$buf}.is_read) { {$fname}.Clear(); foreach(var _int_tmp in tmp_enums_list) {$fname}.Add(({$type->getValue()->getName()}) _int_tmp); }\n";
$str .= "}\n";
$str .= "MetaIO.tmp_enums_list.Clear(); if(!{$buf}.is_read) { foreach(var _enum_tmp in {$fname}) MetaIO.tmp_enums_list.Add((int)_enum_tmp); }\n";
$str .= "MetaIO.Sync({$buf}, MetaIO.tmp_enums_list, \"{$key_name}\", {$opts});\n";
$str .= "if({$buf}.is_read) foreach(var _int_tmp in MetaIO.tmp_enums_list) { {$fname}.Add(({$type->getValue()->getName()}) _int_tmp); }\n";
}
else
$str .= "MetaIO.Sync({$buf}, {$fname}, \"{$key_name}\", {$opts});\n";
@ -570,7 +520,7 @@ function get_diff_related_units(\mtgMetaStruct $struct)
return $result;
}
function get_diff_all_related_structs(\mtgMetaStruct $struct): array
function get_all_related_structs(\mtgMetaStruct $struct): array
{
$result = array_reduce($struct->getFields(), function(array $structs, \mtgMetaField $field) {
if($field->hasToken('nodiff'))
@ -594,7 +544,7 @@ function get_diff_all_related_structs(\mtgMetaStruct $struct): array
foreach($result as $s)
{
$result = array_merge(get_diff_all_related_structs($s), $result);
$result = array_merge(get_all_related_structs($s), $result);
}
return $result;
@ -612,11 +562,6 @@ class AccessorInterfaceField
return $this->field->getName();
}
function hasToken($token)
{
return $this->field->hasToken($token);
}
function getCamelFieldName()
{
$name_parts = explode("_", $this->field->getName());
@ -739,41 +684,3 @@ function get_field_index(\mtgMetaStruct $struct, string $field_name): int
}
return -1;
}
function paginate($total, $step)
{
//pages are returned as an array where each element is in interval [N, Y)
$pages = array();
$steps = (int)($total/$step);
$rest = $total % $step;
for($i=1;$i<=$steps;++$i)
$pages[] = array(($i-1)*$step, $i*$step);
if($rest != 0)
$pages[] = array(($i-1)*$step, ($i-1)*$step + $rest);
return $pages;
}
//slices array like this: [[idx0,[..]], [idx1,[..]], ...]
function slice_units(array $units, $max)
{
$pages = paginate(sizeof($units), $max);
$sliced = array();
$units_keys = array_keys($units);
foreach($pages as $idx => $page)
{
$slice = array();
for($i = $page[0];$i<$page[1];++$i)
{
$slice[] = $units[$units_keys[$i]];
}
$sliced[$idx] = $slice;
}
return $sliced;
}

View File

@ -1,7 +1,6 @@
//THIS FILE IS GENERATED AUTOMATICALLY, DO NOT TOUCH IT!
using System.Collections.Generic;
using metagen;
using System;
{%- import "macro.twig" as macro -%}
@ -9,9 +8,7 @@ using System;
namespace {{namespace}} {
{% endif %}
{%- for ai in get_all_declarable_accessor_interfaces(meta) ~%}
{{ macro.decl_accessor_interface(ai) }}
{%- endfor ~%}
{{ macro.decl_units(meta) }}
static public class AutogenBundle
{

View File

@ -1,16 +0,0 @@
//THIS FILE IS GENERATED AUTOMATICALLY, DO NOT TOUCH IT!
using System.Collections.Generic;
using metagen;
using System;
{%- import "macro.twig" as macro -%}
{% if namespace is defined ~%}
namespace {{namespace}} {
{% endif %}
{{ macro.decl_units(slice_units) }}
{% if namespace is defined ~%}
} //namespace {{namespace}}
{% endif %}

View File

@ -1,6 +1,6 @@
{% macro decl_units(units) %}
{% macro decl_units(meta) %}
{%- for u in units ~%}
{%- for u in meta.getunits ~%}
{%- if u.object is instanceof('\\mtgMetaStruct') -%}
{{ _self.decl_struct(u.object) }}
{%- elseif u.object is instanceof('\\mtgMetaEnum') -%}
@ -10,6 +10,10 @@
{%- endif ~%}
{%- endfor ~%}
{%- for ai in get_all_declarable_accessor_interfaces(meta) ~%}
{{ _self.decl_accessor_interface(ai) }}
{%- endfor ~%}
{% endmacro %}
{% macro decl_struct(o, extra = '') %}
@ -156,27 +160,17 @@ public FieldsMask fields_mask;
{%- macro decl_struct_field(o, f) -%}
{{_self.attributes(f)}}
public {{f.type|cs_type(f)|obscure_type(f)}} {{f.name}} {% if not has_token(o, 'POD') -%} {{_self.decl_init_value(f)}} {%- endif -%};
public {{f.type|cs_type|obscure_type(f)}} {{f.name}} {% if not has_token(o, 'POD') -%} {{_self.decl_init_value(f)}} {%- endif -%};
{%- endmacro -%}
{% macro decl_init_value(f) %}
{%- if f.type is instanceof('\\mtgBuiltinType') -%}
{%- if f.type.isstring -%}
{% if has_token(f, 'flt_i18n') -%}
{%- if has_token(f, 'default') -%}
= new({{token(f, 'default')}})
{%- else -%}
= new()
{%- endif -%}
{%- else -%}
= ""
{%- endif ~%}
{%- endif -%}
{%- if f.type.isstring -%} = ""{%- endif -%}
{%- else -%}
{%- if has_token(f, 'default') and token(f, 'default') == 'null' -%}
/*null*/
{%- else -%}
= new {{f.type|cs_type(f)}}()
= new {{f.type|cs_type}}()
{%- endif -%}
{%- endif -%}
{% endmacro %}
@ -186,11 +180,7 @@ public {{f.type|cs_type(f)|obscure_type(f)}} {{f.name}} {% if not has_token(o, '
base.Reset();
{%- endif -%}
{%- for f in o.fields ~%}
{% if has_token(f, 'flt_i18n') -%}
{{f.name}}?.Reset();
{%- else ~%}
{{var_reset(f.name, f.type, token_or(f, 'default', null))}}
{%- endif ~%}
{%- endfor -%}
{%- if has_token(o, 'bitfields') ~%}
ResetFieldMask();
@ -198,12 +188,6 @@ ResetFieldMask();
{% endmacro %}
{%- macro attributes(o) %}
{% if has_token(o, "cs_obsolete") %}
/// <summary>
/// {{token(o, "cs_obsolete")}}
/// </summary>
[Obsolete]
{% endif %}
{%- if has_token(o, 'cs_attributes') -%}
{%- for attr in token(o, 'cs_attributes')|split(',') -%}
[{{attr}}]
@ -319,7 +303,14 @@ base.SyncFields(ctx);
int fields_amount = {{fields_count(o)}};
var primary_id_mask = FieldsMask.MakeClean(fields_amount);
SetPrimaryFieldsChanged(ref primary_id_mask);
return FieldsMask.CheckContentsChanged(fields_amount, ref primary_id_mask, ref fields_mask);
for(int i = 0; i < fields_amount; i++)
{
bool is_primary_id = primary_id_mask.IsDirty(i);
if(!is_primary_id && fields_mask.IsDirty(i))
return true;
}
return false;
}
public void SetDirtyMask()
@ -358,20 +349,13 @@ public enum {{o.name}}
}
{% endmacro %}
{% macro decl_rpc(o, is_global = true) %}
{% macro decl_rpc(o) %}
{% if is_global %}
{{_self.decl_struct(o.req)}}
{{_self.decl_struct(o.rsp)}}
{% endif %}
public class {{o.name}} : IRpc
{
{% if not is_global %}
{{_self.decl_struct(o.req)}}
{{_self.decl_struct(o.rsp)}}
{% endif %}
public IRpcError error = null;
public {{o.req.name}} req = new {{o.req.name}}();
public {{o.rsp.name}} rsp = new {{o.rsp.name}}();
@ -468,7 +452,7 @@ public class {{o.name}} : IRpc
}
else if(old > item)
{
item.SetDirtyMaskDeep();
item.SetDirtyMask();
if(changed != null)
changed.Add(item);
i++;
@ -492,7 +476,7 @@ public class {{o.name}} : IRpc
for(; i < items.Count; i++)
{
var item = items[i];
item.SetDirtyMaskDeep();
item.SetDirtyMask();
if(changed != null)
changed.Add(item);
has_diff = true;
@ -541,48 +525,10 @@ public class {{o.name}} : IRpc
{{has_token(o, 'bitfields')?'CompareAndMarkFields':'IsEqual'}}
{%- endmacro %}
{% macro field_compare_array_bitfields_nested_diffable(o, f, field_idx) %}
{
int {{f.name}}_count_a = a.{{f.name}} == null ? 0 : a.{{f.name}}.Count;
int {{f.name}}_count_b = b.{{f.name}} == null ? 0 : b.{{f.name}}.Count;
{%- macro field_compare(o, f, field_idx) -%}
//For NESTED arrays of DIFFABLE structs:
//for DB compatibility we must add the WHOLE ARRAY to the diff if ANY ELEMENT of the array has changed.
//That means marking each element's fields_mask as dirty
//Changed size means the array has changed, no furhter checks required.
bool {{f.name}}_equal = {{f.name}}_count_a == {{f.name}}_count_b;
//For same size arrays - check contents for changes.
if({{f.name}}_equal)
{
for(int i=0;i<{{f.name}}_count_a && i<{{f.name}}_count_b;++i)
{
var tmp_{{f.name}} = a.{{f.name}}[i];
if(!CompareAndMarkFields(ref tmp_{{f.name}}, b.{{f.name}}[i]))
{
{{f.name}}_equal = false;
break;
}
}
}
//If change detected - mark top-level field as changed and add the whole array to the diff
if(!{{f.name}}_equal)
{
MetaIO.SetFieldDirty(ref a.fields_mask, {{field_idx}});
for(int i=0; i<{{f.name}}_count_a;++i)
{
var tmp_{{f.name}} = a.{{f.name}}[i];
tmp_{{f.name}}.SetDirtyMaskDeep();
a.{{f.name}}[i] = tmp_{{f.name}};
is_equal = false;
}
}
}
{%- endmacro %}
{% macro field_compare_array_bitfields_nested_plain(o, f, field_idx) %}
{% if f.type is instanceof('\\mtgArrType') ~%}
{% if has_token(o, 'bitfields') ~%}
if(a.{{f.name}} == null ||
b.{{f.name}} == null ||
a.{{f.name}}.Count != b.{{f.name}}.Count)
@ -596,7 +542,7 @@ public class {{o.name}} : IRpc
{
{%- if f.type.value is instanceof('\\mtgMetaStruct') ~%}
var tmp_{{f.name}} = a.{{f.name}}[i];
if(!{{_self.compare_func_name(f.type.value)}}(ref tmp_{{f.name}}, b.{{f.name}}[i]))
if(!IsEqual(ref tmp_{{f.name}}, b.{{f.name}}[i]))
{
MetaIO.SetFieldDirty(ref a.fields_mask, {{field_idx}});
is_equal = false;
@ -612,9 +558,7 @@ public class {{o.name}} : IRpc
{%- endif -%}
}
}
{%- endmacro %}
{% macro field_compare_array_plain(o, f, field_idx) %}
{% else ~%}
if(a.{{f.name}} == null ||
b.{{f.name}} == null ||
a.{{f.name}}.Count != b.{{f.name}}.Count)
@ -623,31 +567,18 @@ public class {{o.name}} : IRpc
{
{%- if f.type.value is instanceof('\\mtgMetaStruct') ~%}
var tmp_{{f.name}} = a.{{f.name}}[i];
if(!{{_self.compare_func_name(f.type.value)}}(ref tmp_{{f.name}}, b.{{f.name}}[i]))
if(!IsEqual(ref tmp_{{f.name}}, b.{{f.name}}[i]))
return false;
{%- else -%}
if(a.{{f.name}}[i] != b.{{f.name}}[i])
return false;
{%- endif -%}
}
{%- endmacro %}
{%- macro field_compare(o, f, field_idx) -%}
{% if f.type is instanceof('\\mtgArrType') ~%}
{% if has_token(o, 'bitfields') ~%}
{% if has_token(f.type.value, 'bitfields') ~%}
{{_self.field_compare_array_bitfields_nested_diffable(o, f, field_idx)}}
{% else ~%}
{{_self.field_compare_array_bitfields_nested_plain(o, f, field_idx)}}
{% endif ~%}
{% else ~%}
{{_self.field_compare_array_plain(o, f, field_idx)}}
{% endif ~%}
{% elseif f.type is instanceof('\\mtgMetaStruct') ~%}
var tmp_{{f.name}} = a.{{f.name}}[i];
if(!{{_self.compare_func_name(f.type.value)}}(ref tmp_{{f.name}}, b.{{f.name}}))
if(!IsEqual(ref tmp_{{f.name}}, b.{{f.name}}))
{% if has_token(o, 'bitfields') ~%}
{
MetaIO.SetFieldDirty(ref a.fields_mask, {{field_idx}});
@ -737,12 +668,12 @@ public interface {{ai.interfacename}}
{
{%- for aif in ai.fields ~%}
{% if aif.is_accessor -%}
{{aif.field.type|cs_type(aif)}} Get{{aif.camelfieldname}}();
void Set{{aif.camelfieldname}}({{aif.field.type|cs_type(aif)}} v);
{{aif.field.type|cs_type}} Get{{aif.camelfieldname}}();
void Set{{aif.camelfieldname}}({{aif.field.type|cs_type}} v);
{% endif -%}
{%- if aif.is_propget -%}
{{aif.field.type|cs_type(aif)}} {{aif.camelfieldname}} {
{{aif.field.type|cs_type}} {{aif.camelfieldname}} {
{%- if aif.is_propget -%}
get;
{%- endif %}
@ -759,18 +690,18 @@ public interface {{ai.interfacename}}
{%- for ai in get_accessor_interfaces(o) ~%}
{%- for aif in ai.fields ~%}
{% if aif.is_accessor %}
public {{aif.field.type|cs_type(aif)}} Get{{aif.camelfieldname}}()
public {{aif.field.type|cs_type}} Get{{aif.camelfieldname}}()
{
return {{aif.field.name}};
}
public void Set{{aif.camelfieldname}}({{aif.field.type|cs_type(aif)}} v)
public void Set{{aif.camelfieldname}}({{aif.field.type|cs_type}} v)
{
this.{{aif.field.name}} = v;
}
{% endif %}
{% if aif.is_propget or aif.is_propset %}
public {{aif.field.type|cs_type(aif)}} {{aif.camelfieldname}} {
public {{aif.field.type|cs_type}} {{aif.camelfieldname}} {
{%- if aif.is_propget -%}
get => this.{{aif.field.name}};
{%- endif -%}
@ -782,3 +713,4 @@ public interface {{ai.interfacename}}
{% endfor %}
{%- endfor ~%}
{% endmacro %}