setCache($cache_dir); $twig->addGlobal('meta', $meta); $options['meta'] = $meta; if(!isset($options['namespace'])) $options['namespace'] = 'BitGames.Autogen'; return $twig->render('codegen_bundle.twig', $options); } function get_twig(array $inc_path = []) : \Twig\Environment { array_unshift($inc_path, __DIR__ . "/../tpl/"); $loader = new \Twig\Loader\FilesystemLoader($inc_path); $twig = new \Twig\Environment($loader, [ 'debug' => true, 'autoescape' => false, 'strict_variables' => true] ); $twig->addExtension(new \Twig\Extension\DebugExtension()); _add_twig_support($twig); return $twig; } function supported_tokens() : array { return [ 'POD', 'default', 'alias', 'virtual', 'bitfields', 'cloneable', 'obscured', 'diffable', //TODO: this should be in a different package as a plugin 'table_pkey', 'cs_embed', 'cs_implements', 'cs_attributes', 'cs_accessor_interface', 'cs_propget_interface', 'cs_propset_interface', 'cs_propgetset_interface', //TODO: //'i18n' ]; } function _add_twig_support(\Twig\Environment $twig) { $twig->addTest(new \Twig\TwigTest('instanceof', function($obj, $class) { return (new \ReflectionClass($class))->isInstance($obj); } )); $twig->addFunction(new \Twig\TwigFunction('Error', function($e) { throw new Exception($e); } )); $twig->addFunction(new \Twig\TwigFunction('has_token', function($o, $token) { return (($o instanceof \mtgMetaUnit) || ($o instanceof \mtgMetaField)) && $o->hasToken($token); } )); $twig->addFunction(new \Twig\TwigFunction('has_token_in_parent', function($o, $token) { return $o->hasTokenInParent($token); } )); $twig->addFunction(new \Twig\TwigFunction('token', function($o, $name) { return $o->getToken($name); } )); $twig->addFunction(new \Twig\TwigFunction('token_or', function($o, $name, $v) { if($o->hasToken($name)) return $o->getToken($name); else return $v; } )); $twig->addFunction(new \Twig\TwigFunction('field_index', function(\mtgMetaStruct $s, string $f): int { $idx = get_field_index($s, $f); if($idx == -1) { throw new Exception("field `$f` not found in `{$s->getName()}`"); } return $idx; } )); $twig->addFunction(new \Twig\TwigFunction('find_struct', function(string $name) use ($twig): \mtgMetaStruct { $meta = $twig->getGlobals()['meta']; $unit = $meta->findUnit($name); return $unit->object; } )); $twig->addFilter(new \Twig\TwigFilter('cs_type', function($type) { return cs_type($type); } )); $twig->addFilter(new \Twig\TwigFilter('cs_type_prefix', function($type) { return cs_type_prefix($type); } )); $twig->addFilter(new \Twig\TwigFilter('obscure_type', function($type_str, $fld) { return obscure_type($type_str, $fld); } )); $twig->addFunction(new \Twig\TwigFunction('var_reset', function($name, $type, $default) { return var_reset($name, $type, $default); } )); $twig->addFunction(new \Twig\TwigFunction('var_sync', function($name, $type, $buf, $tokens, $opts) { return var_sync($name, $type, $buf, $tokens, $opts); } )); $twig->addFunction(new \Twig\TwigFunction('get_sync_opts', function($o, $name) { return get_sync_opts($o, $name); } )); $twig->addFunction(new \Twig\TwigFunction('fields_count', function($o) { return fields_count($o); } )); $twig->addFunction(new \Twig\TwigFunction('is_primary_field', function($o, $f) { return is_primary_field($o, $f); } )); $twig->addFunction(new \Twig\TwigFunction('get_diff_related_units', function($o) { return get_diff_all_related_structs($o); } )); $twig->addFunction(new \Twig\TwigFunction('get_all_declarable_accessor_interfaces', function($o) { return get_all_declarable_accessor_interfaces($o); } )); $twig->addFunction(new \Twig\TwigFunction('get_accessor_interfaces', function($o) { return get_accessor_interfaces($o); } )); } function cs_type(\mtgType $type) { if($type instanceof \mtgBuiltinType || $type instanceof \mtgUserType) return cs_simple_type($type); else if($type instanceof \mtgArrType) return "List<" . cs_simple_type($type->getValue()) . ">"; else throw new Exception("Unknown type '{$type->getName()}'"); } function cs_simple_type(\mtgType $type) { if($type instanceof \mtgBuiltinType) { switch($type->getName()) { case "int8": return "sbyte"; case "uint8": return "byte"; case "int16": return "short"; case "uint16": return "ushort"; case "int32": case "int": return "int"; case "uint32": case "uint": return "uint"; case "float": return "float"; case "double": return "double"; case "uint64": return "ulong"; case "int64": return "long"; case "string": return "string"; case "bool": return "bool"; case "blob": return "byte[]"; } throw new Exception("Unknown type '{$type}'"); } if($type instanceof \mtgUserType && $type->hasToken("bhl_native_class")) return $type->getToken("bhl_native_class"); return $type->getName(); } function cs_type_prefix(\mtgType $type) { switch($type->getName()) { case "float": return "Float"; case "double": return "Double"; case "uint64": return "U64"; case "int64": return "I64"; case "uint": case "uint32": return "U32"; case "uint16": return "U16"; case "uint8": return "U8"; case "int": case "int32": return "I32"; case "int16": return "I16"; case "int8": return "I8"; case "string": return "String"; case "bool": return "Bool"; case "blob": return "Blob"; default: throw new Exception("Unknown type '{$type->getName()}'"); } } function obscure_type($type_str, $fld) { if($fld->hasToken('obscured')) { if($fld->getType() instanceof \mtgBuiltinType && $fld->getType()->isUint32()) return "CodeStage.AntiCheat.ObscuredTypes.ObscuredUInt"; else if($fld->getType() instanceof \mtgBuiltinType && $fld->getType()->isUint64()) return "CodeStage.AntiCheat.ObscuredTypes.ObscuredULong"; else if($fld->getType() instanceof \mtgBuiltinType && $fld->getType()->isInt64()) return "CodeStage.AntiCheat.ObscuredTypes.ObscuredLong"; else if($fld->getType() instanceof \mtgBuiltinType && $fld->getType()->isFloat()) return "CodeStage.AntiCheat.ObscuredTypes.ObscuredFloat"; else throw new Exception("Not supported obscured type: " . $fld->getType()); } return $type_str; } function var_reset($name, \mtgType $type, $default = null) { $str = ''; if($type instanceof \mtgBuiltinType) { if($type->isNumeric()) { $str = $name; //NOTE: numeric check is against str2num if($default && is_numeric($default)) { if($type->isFloat()) $default .= "f"; $str .= " = $default;"; } else $str .= " = 0;"; } else if($type->isString()) { $str = $name; if($default) { $str .= " = \"".trim($default, '"')."\";"; } else $str .= ' = "";'; } else if($type->isBool()) { $str = $name; if($default) { $str .= " = ".trim($default, '"').";"; } else $str .= ' = false;'; } else if($type->isBlob()) { $str = $name; if($default) { $str .= " = ".trim($default, '"').";"; } else $str .= ' = null;'; } else throw new Exception("Unknown type '$type'"); } else if($type instanceof \mtgArrType) { $str = "MetaIO.ClearList(ref $name);"; if($default) { $default_arr = is_array($default) ? $default : json_decode($default, true); if(!is_array($default_arr)) throw new Exception("Bad default value for array: '$default'"); if($default_arr) { $type_str = cs_type($type->getValue()); $str .= "{ $type_str tmp__"; if(is_nullable_type($type->getValue())) $str .= " = null"; $str .= ";"; foreach($default_arr as $v) { $str .= var_reset("tmp__", $type->getValue(), $v); $str .= "$name.Add(tmp__);"; } $str .= "}"; } } } else if($type instanceof \mtgMetaEnum) { if($default) $str = "$name = ".cs_type($type).".".trim($default,'"')."; "; else $str = "$name = new ".cs_type($type)."(); "; } else if($type instanceof \mtgMetaStruct) { $str = ""; if(!is_null_str($default)) { $is_pod = $type->hasToken('POD'); if($is_pod) $str .= "$name.Reset(); "; else $str .= "MetaIO.Reset(ref $name); "; } if($default) { $default = is_array($default) ? $default : json_decode($default, true); if(is_array($default)) { foreach($default as $k => $v) { $kf = $type->getField($k); $str .= var_reset("$name." . $kf->getName(), $kf->getType(), $v); } } else if($default === null) $str .= "$name = null; "; else throw new Exception("Bad default value for struct: " . var_export($default, true)); } } else throw new Exception("Bad type '{$type->getName()}'"); return $str; } function is_null_str($default) { return is_string($default) && json_decode($default, true) === null; } function var_sync($fname, \mtgType $type, $buf, array $tokens, $opts) { $key_name = array_key_exists('alias', $tokens) ? $tokens['alias'] : $fname; $str = ''; if($type instanceof \mtgBuiltinType) { $str .= "MetaIO.Sync({$buf}, ref {$fname}, \"{$key_name}\", {$opts});\n"; } else if($type instanceof \mtgMetaStruct) { if(array_key_exists('virtual', $tokens)) $str .= "{$fname} = MetaIO.SyncGeneric({$buf}, {$fname}, \"{$key_name}\", {$opts});\n"; else $str .= "MetaIO.Sync({$buf}, ref {$fname}, \"{$key_name}\", {$opts});\n"; } else if($type instanceof \mtgMetaEnum) { $str .= "int __tmp_{$fname} = (int)$fname;\n"; $str .= "MetaIO.Sync({$buf}, ref __tmp_{$fname}, \"{$key_name}\", {$opts});\n"; $str .= "if($buf.is_read) {$fname} = ({$type->getName()})__tmp_{$fname};\n"; } else if($type instanceof \mtgArrType) { if(array_key_exists('virtual', $tokens)) $str .= "MetaIO.SyncGeneric({$buf}, {$fname}, \"{$key_name}\", {$opts});\n"; else { if($type->getValue() instanceof \mtgMetaEnum) { $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"; } } else throw new Exception("Unknown type '{$type->getName()}'"); return $str; } function get_sync_opts(\mtgMetaStruct $struct, string $bitctx_name) { $opts = array(); if($struct->hasToken("bitfields")) $opts[] = "$bitctx_name.GetNextOpts()"; if(count($opts) == 0) return "0"; return implode(" | ", $opts); } function fields_count(\mtgMetaStruct $struct) { //NOTE: why not using fields_count(..) here as well? // (becase they include i18n fields already?) $count = count($struct->getFields()); $curr = $struct->getParent(); while($curr) { $count += fields_count_self($curr); $curr = $curr->getParent(); } return $count; } function fields_count_self(\mtgMetaStruct $struct) { $fields = $struct->getFields(); $count = 0; foreach($fields as $field) { //TODO: do we still need this if($field->hasToken('i18n')) $count += 2; else $count += 1; } return $count; } function is_primary_field(\mtgMetaStruct $struct, $fld) { $primary_fields = array(); if($struct->hasToken("table_pkey")) $primary_fields = explode(",", $struct->getToken("table_pkey")); return in_array($fld->getName(), $primary_fields); } function is_nullable_type(\mtgType $type) { return $type instanceof \mtgArrType || ($type instanceof \mtgMetaStruct && !$type->hasToken('POD')); } function get_diff_related_units(\mtgMetaStruct $struct) { $result = array(); foreach($struct->getFields() as $field) { if($field->hasToken('nodiff')) continue; if($field->getType() instanceof \mtgMetaStruct) $result[] = $field->getType(); else if($field->getType() instanceof \mtgArrType) $result[] = $field->getType()->getValue(); } return $result; } function get_diff_all_related_structs(\mtgMetaStruct $struct): array { $result = array_reduce($struct->getFields(), function(array $structs, \mtgMetaField $field) { if($field->hasToken('nodiff')) { return $structs; } $type = $field->getType(); if($type instanceof \mtgArrType) { $type = $type->getValue(); } if($type instanceof \mtgMetaStruct) { $structs[] = $type; } return $structs; }, []); foreach($result as $s) { $result = array_merge(get_diff_all_related_structs($s), $result); } return $result; } class AccessorInterfaceField { public $field; public $is_propget; public $is_propset; public $is_accessor; function getUID() { return $this->field->getName(); } function getCamelFieldName() { $name_parts = explode("_", $this->field->getName()); $uc_parts = array(); foreach($name_parts as $part) $uc_parts[] = ucfirst($part); return implode($uc_parts); } } class AccessorInterface { public $cs_interface_name; public $fields = array(); function getUID() { return $this->getInterfaceName(); } function getInterfaceName() { return $this->cs_interface_name; } } //NOTE: by default, accessor interface declarations will be generated in the autogen bundle. //If you want your metagen struct to implement some existing C# inteface //use quotes and prefix the fully-qualified interface name with "!", e.g. like this: // // ticket : string @cs_accessor_interface:"!BitGames.ServerIntegration.IRpcWithAuth" // function get_accessor_interfaces(\mtgMetaStruct $struct) { return get_accessor_interfaces_ex($struct, /* declarable_only = */ false); } function get_accessor_interfaces_declarable(\mtgMetaStruct $struct) { return get_accessor_interfaces_ex($struct, /* declarable_only = */ true); } function get_accessor_interfaces_ex(\mtgMetaStruct $struct, $declarable_only) { $ifs = array(); $external_mark = '"!'; foreach($struct->getFields() as $field) { $is_accessor = $field->hasToken('cs_accessor_interface'); $is_propget = $field->hasToken('cs_propget_interface'); $is_propset = $field->hasToken('cs_propset_interface'); $is_propgetset = $field->hasToken('cs_propgetset_interface'); if($is_accessor || $is_propget || $is_propset || $is_propgetset) { $ai = new AccessorInterface(); if($is_accessor) $ai->cs_interface_name = $field->getToken('cs_accessor_interface'); elseif($is_propget) $ai->cs_interface_name = $field->getToken('cs_propget_interface'); elseif($is_propset) $ai->cs_interface_name = $field->getToken('cs_propset_interface'); elseif($is_propgetset) $ai->cs_interface_name = $field->getToken('cs_propgetset_interface'); $is_external = strpos($ai->cs_interface_name, $external_mark) === 0; if($is_external) { $ai->cs_interface_name = trim($ai->cs_interface_name, $external_mark); if($declarable_only) continue; } if(array_key_exists($ai->getUID(), $ifs)) $ai = $ifs[$ai->getUID()]; $aif = new AccessorInterfaceField(); $aif->field = $field; if(array_key_exists($aif->getUID(), $ai->fields)) $aif = $ai->fields[$aif->getUID()]; $aif->is_propget |= $is_propgetset || $is_propget; $aif->is_propset |= $is_propgetset || $is_propset; $aif->is_accessor |= $is_accessor; $ai->fields[$aif->getUID()] = $aif; $ifs[$ai->getUID()] = $ai; } } return $ifs; } function get_all_declarable_accessor_interfaces(\mtgMetaInfo $info) { $all = array(); foreach($info->getUnits() as $unit) { if($unit->object instanceof \mtgMetaStruct) $all = array_merge($all, get_accessor_interfaces_declarable($unit->object)); else if($unit->object instanceof \mtgMetaRPC) { $all = array_merge($all, get_accessor_interfaces_declarable($unit->object->getReq())); $all = array_merge($all, get_accessor_interfaces_declarable($unit->object->getRsp())); } } return $all; } function get_field_index(\mtgMetaStruct $struct, string $field_name): int { $idx = -1; foreach($struct->getFields() as $field) { $idx++; if($field->getName() == $field_name) { return $idx; } } return -1; }