From e224f58a3b4de745b85c353fef3ce1f445a0609e Mon Sep 17 00:00:00 2001 From: Kybxd <627940450@qq.com> Date: Wed, 30 Apr 2025 10:33:33 +0800 Subject: [PATCH 1/6] feat: init proj feat: update go.mod and readme feat: add some template code feat: load from bin feat: only support json and bin feat: hub feat: load methods feat: add filter huboptions feat: a simple example feat: git ignore feat: remove LoadMode feat: update readme feat: csharp ordered map feat: add index feat: encapsulate messager container fix(index): correct csharp index logic and add test examples feat: update index and version feat: simplify some logic feat: escapeIdentifier feat: best practice feat: go mod tidy feat: simplify code and remove gen plugin as parameters feat: use lower camel case for private variables feat: OrderedMapValue not null feat: add MessageParser instead of generic types feat: add test case for load bin feat: classes feat: options and newline feat: delete settings feat: add bat scripts feat: support ordered index feat: update bin ext fix: generate file path feat: use *.pc.cs file ext feat: thread-safe messager container feat: use template to genearate hub feat: update scripts feat: add readonly feat: orderedmap feat: index feat: revert feat: ordered index feat: ident feat: IComparable feat: map key feat: map key feat: use tuple feat: leveld index feat(helper): enhance C# class type parsing and namespace handling --- .gitignore | 4 + README.md | 13 + cmd/protoc-gen-csharp-tableau-loader/embed.go | 34 + .../embed/Load.pc.cs | 221 +++ .../embed/Util.pc.cs | 61 + .../embed/templates/Hub.pc.cs.tpl | 146 ++ .../helper/helper.go | 278 ++++ .../helper/keyword.go | 117 ++ cmd/protoc-gen-csharp-tableau-loader/hub.go | 21 + .../indexes/generator.go | 154 +++ .../indexes/index.go | 343 +++++ .../indexes/ordered_index.go | 340 +++++ cmd/protoc-gen-csharp-tableau-loader/main.go | 38 + .../messager.go | 173 +++ .../orderedmap/ordered_map.go | 205 +++ internal/options/options.go | 1 + test/csharp-tableau-loader/Loader.csproj | 15 + test/csharp-tableau-loader/Program.cs | 107 ++ .../csharp-tableau-loader.sln | 24 + test/csharp-tableau-loader/gen.bat | 83 ++ test/csharp-tableau-loader/gen.sh | 72 + .../tableau/HeroConf.pc.cs | 176 +++ test/csharp-tableau-loader/tableau/Hub.pc.cs | 209 +++ .../tableau/IndexConf.pc.cs | 1215 +++++++++++++++++ .../tableau/ItemConf.pc.cs | 630 +++++++++ test/csharp-tableau-loader/tableau/Load.pc.cs | 228 ++++ .../tableau/PatchConf.pc.cs | 190 +++ .../tableau/TestConf.pc.cs | 889 ++++++++++++ test/csharp-tableau-loader/tableau/Util.pc.cs | 68 + test/proto/hero_conf.proto | 2 +- test/testdata/bin/HeroConf.binpb | 11 + test/testdata/conf/ItemConf.json | 22 +- 32 files changed, 6087 insertions(+), 3 deletions(-) create mode 100644 cmd/protoc-gen-csharp-tableau-loader/embed.go create mode 100644 cmd/protoc-gen-csharp-tableau-loader/embed/Load.pc.cs create mode 100644 cmd/protoc-gen-csharp-tableau-loader/embed/Util.pc.cs create mode 100644 cmd/protoc-gen-csharp-tableau-loader/embed/templates/Hub.pc.cs.tpl create mode 100644 cmd/protoc-gen-csharp-tableau-loader/helper/helper.go create mode 100644 cmd/protoc-gen-csharp-tableau-loader/helper/keyword.go create mode 100644 cmd/protoc-gen-csharp-tableau-loader/hub.go create mode 100644 cmd/protoc-gen-csharp-tableau-loader/indexes/generator.go create mode 100644 cmd/protoc-gen-csharp-tableau-loader/indexes/index.go create mode 100644 cmd/protoc-gen-csharp-tableau-loader/indexes/ordered_index.go create mode 100644 cmd/protoc-gen-csharp-tableau-loader/main.go create mode 100644 cmd/protoc-gen-csharp-tableau-loader/messager.go create mode 100644 cmd/protoc-gen-csharp-tableau-loader/orderedmap/ordered_map.go create mode 100644 test/csharp-tableau-loader/Loader.csproj create mode 100644 test/csharp-tableau-loader/Program.cs create mode 100644 test/csharp-tableau-loader/csharp-tableau-loader.sln create mode 100644 test/csharp-tableau-loader/gen.bat create mode 100644 test/csharp-tableau-loader/gen.sh create mode 100644 test/csharp-tableau-loader/tableau/HeroConf.pc.cs create mode 100644 test/csharp-tableau-loader/tableau/Hub.pc.cs create mode 100644 test/csharp-tableau-loader/tableau/IndexConf.pc.cs create mode 100644 test/csharp-tableau-loader/tableau/ItemConf.pc.cs create mode 100644 test/csharp-tableau-loader/tableau/Load.pc.cs create mode 100644 test/csharp-tableau-loader/tableau/PatchConf.pc.cs create mode 100644 test/csharp-tableau-loader/tableau/TestConf.pc.cs create mode 100644 test/csharp-tableau-loader/tableau/Util.pc.cs create mode 100644 test/testdata/bin/HeroConf.binpb diff --git a/.gitignore b/.gitignore index 9c0a1db2..de4ff9c5 100644 --- a/.gitignore +++ b/.gitignore @@ -39,7 +39,11 @@ node_modules cmd/protoc-gen-cpp-tableau-loader/protoc-gen-cpp-tableau-loader cmd/protoc-gen-go-tableau-loader/protoc-gen-go-tableau-loader +cmd/protoc-gen-csharp-tableau-loader/protoc-gen-csharp-tableau-loader test/go-tableau-loader/go-tableau-loader _lab/ts/src/protoconf coverage.txt +!test/testdata/bin/ +test/csharp-tableau-loader/protoconf + diff --git a/README.md b/README.md index 4ff8e00d..6136676f 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,19 @@ The official config loader for [Tableau](https://github.com/tableauio/tableau). - [Protocol Buffers Go Reference](https://protobuf.dev/reference/go/) +## C# + +### Requirements + +- dotnet-sdk-8.0 + +### Test + +- Install: **dotnet-sdk-8.0** +- Change dir: `cd test/csharp-tableau-loader` +- Generate protoconf: `sh gen.sh` +- Test: `dotnet run` + ## TypeScript ### Requirements diff --git a/cmd/protoc-gen-csharp-tableau-loader/embed.go b/cmd/protoc-gen-csharp-tableau-loader/embed.go new file mode 100644 index 00000000..9961feda --- /dev/null +++ b/cmd/protoc-gen-csharp-tableau-loader/embed.go @@ -0,0 +1,34 @@ +package main + +import ( + "embed" + "path" + + "github.com/tableauio/loader/cmd/protoc-gen-csharp-tableau-loader/helper" + "google.golang.org/protobuf/compiler/protogen" +) + +//go:embed embed/* +var efs embed.FS + +// generateEmbed generates related registry files. +func generateEmbed(gen *protogen.Plugin) { + entries, err := efs.ReadDir("embed") + if err != nil { + panic(err) + } + for _, entry := range entries { + if entry.IsDir() { + continue + } + + g := gen.NewGeneratedFile(entry.Name(), "") + helper.GenerateFileHeader(gen, nil, g, version) + // refer: [embed: embed path on different OS cannot open file](https://github.com/golang/go/issues/45230) + content, err := efs.ReadFile(path.Join("embed", entry.Name())) + if err != nil { + panic(err) + } + g.P(string(content)) + } +} diff --git a/cmd/protoc-gen-csharp-tableau-loader/embed/Load.pc.cs b/cmd/protoc-gen-csharp-tableau-loader/embed/Load.pc.cs new file mode 100644 index 00000000..23406389 --- /dev/null +++ b/cmd/protoc-gen-csharp-tableau-loader/embed/Load.pc.cs @@ -0,0 +1,221 @@ +using pb = global::Google.Protobuf; +using pbr = global::Google.Protobuf.Reflection; +namespace Tableau +{ + /// + /// Load provides functions for loading and parsing protobuf messages from files. + /// + public static class Load + { + /// + /// ReadFunc reads the config file and returns its content. + /// + public delegate byte[] ReadFunc(string path); + /// + /// LoadFunc defines a func which can load message's content based on the + /// given descriptor, path, format, and options. + /// + public delegate pb::IMessage? LoadFunc(pbr::MessageDescriptor desc, string dir, Format fmt, in MessagerOptions? options); + + /// + /// BaseOptions is the common options for both global-level and messager-level options. + /// + public class BaseOptions + { + /// + /// Whether to ignore unknown JSON fields during parsing. + /// + public bool? IgnoreUnknownFields { get; set; } + /// + /// You can specify custom read function to read a config file's content. + /// Default is File.ReadAllBytes. + /// + public ReadFunc? ReadFunc { get; set; } + /// + /// You can specify custom load function to load a messager's content. + /// Default is LoadMessager. + /// + public LoadFunc? LoadFunc { get; set; } + } + + /// + /// Options is the global-level options, which contains both global-level and + /// messager-level options. + /// + public class Options : BaseOptions + { + /// + /// MessagerOptions maps each messager name to a MessagerOptions. + /// If specified, then the messager will be parsed with the given options directly. + /// + public Dictionary? MessagerOptions { get; set; } + /// + /// ParseMessagerOptionsByName parses messager options with both global-level and + /// messager-level options taken into consideration. + /// + public MessagerOptions ParseMessagerOptionsByName(string name) + { + var mopts = MessagerOptions?.TryGetValue(name, out var val) == true ? (MessagerOptions)val.Clone() : new MessagerOptions(); + mopts.IgnoreUnknownFields ??= IgnoreUnknownFields; + mopts.ReadFunc ??= ReadFunc; + mopts.LoadFunc ??= LoadFunc; + return mopts; + } + } + + /// + /// MessagerOptions defines the options for loading a messager. + /// + public class MessagerOptions : BaseOptions, ICloneable + { + /// + /// Path maps each messager name to a corresponding config file path. + /// If specified, then the main messager will be parsed from the file + /// directly, other than the specified load dir. + /// + public string? Path { get; set; } + + public object Clone() + { + return new MessagerOptions + { + IgnoreUnknownFields = IgnoreUnknownFields, + ReadFunc = ReadFunc, + LoadFunc = LoadFunc, + Path = Path + }; + } + } + + /// + /// LoadMessager loads a protobuf message from the specified file path and format. + /// + public static pb::IMessage? LoadMessager(pbr::MessageDescriptor desc, string path, Format fmt, in MessagerOptions? options = null) + { + var readFunc = options?.ReadFunc ?? File.ReadAllBytes; + byte[] content; + try + { + content = readFunc(path); + } + catch (Exception ex) + { + Util.SetErrMsg($"failed to read {path}: {ex.Message}"); + throw; + } + return Unmarshal(content, desc, fmt, options); + } + + /// + /// LoadMessagerInDir loads a protobuf message from the specified directory and format. + /// It resolves the file path based on the message descriptor name. + /// + public static pb::IMessage? LoadMessagerInDir(pbr::MessageDescriptor desc, string dir, Format fmt, in MessagerOptions? options = null) + { + string name = desc.Name; + string path = ""; + if (options?.Path != null) + { + path = options.Path; + fmt = Util.GetFormat(path); + } + if (path == "") + { + string filename = name + Util.Format2Ext(fmt); + path = Path.Combine(dir, filename); + } + var loadFunc = options?.LoadFunc ?? LoadMessager; + return loadFunc(desc, path, fmt, options); + } + + /// + /// Unmarshal parses the given byte content into a protobuf message based on the specified format. + /// + public static pb::IMessage? Unmarshal(byte[] content, pbr::MessageDescriptor desc, Format fmt, in MessagerOptions? options = null) + { + switch (fmt) + { + case Format.JSON: + try + { + var parser = new pb::JsonParser( + pb::JsonParser.Settings.Default.WithIgnoreUnknownFields(options?.IgnoreUnknownFields ?? false) + ); + return parser.Parse(new StreamReader(new MemoryStream(content)), desc); + } + catch (Exception ex) + { + Util.SetErrMsg($"failed to parse {desc.Name}.json: {ex.Message}"); + throw; + } + case Format.Bin: + try + { + return desc.Parser.ParseFrom(content); + } + catch (Exception ex) + { + Util.SetErrMsg($"failed to parse {desc.Name}.binpb: {ex.Message}"); + throw; + } + default: + Util.SetErrMsg($"unknown format: {fmt}"); + return null; + } + } + } + + /// + /// IMessagerName is an interface that provides the static Name() method for messagers. + /// + public interface IMessagerName + { + static abstract string Name(); + } + + /// + /// Messager is the base class for all generated configuration messagers. + /// It is designed for three goals: + /// 1. Easy use: simple yet powerful accessors. + /// 2. Elegant API: concise and clean functions. + /// 3. Extensibility: Map, OrderedMap, Index, OrderedIndex... + /// + public abstract class Messager + { + /// + /// Stats contains statistics info about loading. + /// + public class Stats + { + /// Total load time consuming. + public TimeSpan Duration; + } + + protected Stats LoadStats = new(); + + /// + /// GetStats returns the loading stats info. + /// + public ref readonly Stats GetStats() => ref LoadStats; + + /// + /// Load fills message from file in the specified directory and format. + /// + public abstract bool Load(string dir, Format fmt, in Load.MessagerOptions? options = null); + + /// + /// Message returns the inner protobuf message data. + /// + public virtual pb::IMessage? Message() => null; + + /// + /// ProcessAfterLoad is invoked after this messager loaded. + /// + protected virtual bool ProcessAfterLoad() => true; + + /// + /// ProcessAfterLoadAll is invoked after all messagers loaded. + /// + public virtual bool ProcessAfterLoadAll(in Hub hub) => true; + } +} \ No newline at end of file diff --git a/cmd/protoc-gen-csharp-tableau-loader/embed/Util.pc.cs b/cmd/protoc-gen-csharp-tableau-loader/embed/Util.pc.cs new file mode 100644 index 00000000..e720eb81 --- /dev/null +++ b/cmd/protoc-gen-csharp-tableau-loader/embed/Util.pc.cs @@ -0,0 +1,61 @@ +namespace Tableau +{ + /// + /// Format specifies the format of the configuration file. + /// + public enum Format + { + Unknown, + JSON, + Bin + } + + /// + /// Util provides common utility functions. + /// + public static class Util + { + [ThreadStatic] private static string? _errMsg; + + /// + /// Get the last error message. + /// + public static string GetErrMsg() => _errMsg ?? ""; + + /// + /// Set the error message. + /// + public static void SetErrMsg(string msg) => _errMsg = msg; + + private const string _unknownExt = ".unknown"; + private const string _jsonExt = ".json"; + private const string _binExt = ".binpb"; + + /// + /// GetFormat returns the Format type determined by the file extension of the given path. + /// + public static Format GetFormat(string path) + { + string ext = Path.GetExtension(path); + return ext switch + { + _jsonExt => Format.JSON, + _binExt => Format.Bin, + _ => Format.Unknown, + }; + } + + /// + /// Format2Ext returns the file extension corresponding to the given format. + /// + public static string Format2Ext(Format fmt) + { + return fmt switch + { + Format.JSON => _jsonExt, + Format.Bin => _binExt, + _ => _unknownExt, + }; + } + } +} \ No newline at end of file diff --git a/cmd/protoc-gen-csharp-tableau-loader/embed/templates/Hub.pc.cs.tpl b/cmd/protoc-gen-csharp-tableau-loader/embed/templates/Hub.pc.cs.tpl new file mode 100644 index 00000000..4d08249f --- /dev/null +++ b/cmd/protoc-gen-csharp-tableau-loader/embed/templates/Hub.pc.cs.tpl @@ -0,0 +1,146 @@ +using pb = global::Google.Protobuf; +namespace Tableau +{ + /// + /// MessagerContainer holds all messager instances and provides fast access. + /// + internal class MessagerContainer(in Dictionary? messagerMap = null) + { + public Dictionary MessagerMap = messagerMap ?? []; + public DateTime LastLoadedTime = DateTime.Now; +{{ range . }} public {{ . }}? {{ . }} = InternalGet<{{ . }}>(messagerMap); +{{ end }} + /// + /// Get returns the messager of type T from the container. + /// + public T? Get() where T : Messager, IMessagerName => InternalGet(MessagerMap); + + private static T? InternalGet(in Dictionary? messagerMap) where T : Messager, IMessagerName => + messagerMap?.TryGetValue(T.Name(), out var messager) == true ? (T)messager : null; + } + + /// + /// Atomic provides a thread-safe wrapper for reference types. + /// + internal class Atomic where T : class + { + private T? _value; + + public T? Value + { + get => Interlocked.CompareExchange(ref _value, null, null); + set => Interlocked.Exchange(ref _value, value); + } + } + + /// + /// HubOptions is the options for Hub. + /// + public class HubOptions + { + /// + /// Filter can only filter in certain specific messagers based on the + /// condition that you provide. + /// + public Func? Filter { get; set; } + } + + /// + /// Hub is the messager manager. It manages loading, accessing, and storing + /// all configuration messagers. + /// + public class Hub(HubOptions? options = null) + { + private readonly Atomic _messagerContainer = new(); + private readonly HubOptions? _options = options; + + /// + /// Load fills messages from files in the specified directory and format. + /// + public bool Load(string dir, Format fmt, in Load.Options? options = null) + { + var messagerMap = NewMessagerMap(); + var opts = options ?? new Load.Options(); + foreach (var kvs in messagerMap) + { + string name = kvs.Key; + if (!kvs.Value.Load(dir, fmt, opts.ParseMessagerOptionsByName(name))) + { + Console.Error.WriteLine($"load {name} failed: {Util.GetErrMsg()}"); + return false; + } + } + var tmpHub = new Hub(); + tmpHub.SetMessagerMap(messagerMap); + foreach (var messager in messagerMap) + { + if (!messager.Value.ProcessAfterLoadAll(tmpHub)) + { + Console.Error.WriteLine($"hub call ProcessAfterLoadAll failed, messager: {messager.Key}"); + return false; + } + } + SetMessagerMap(messagerMap); + return true; + } + + /// + /// GetMessagerMap returns the current messager map. + /// + public IReadOnlyDictionary? GetMessagerMap() => _messagerContainer.Value?.MessagerMap; + + /// + /// SetMessagerMap sets the messager map with thread-safe guarantee. + /// + public void SetMessagerMap(in Dictionary map) => _messagerContainer.Value = new MessagerContainer(map); + + /// + /// Get returns the messager of type T from the hub. + /// + public T? Get() where T : Messager, IMessagerName => _messagerContainer.Value?.Get(); +{{ range . }} + public {{ . }}? Get{{ . }}() => _messagerContainer.Value?.{{ . }}; +{{ end }} + /// + /// GetLastLoadedTime returns the time when hub's messager container was last set. + /// + public DateTime? GetLastLoadedTime() => _messagerContainer.Value?.LastLoadedTime; + + /// + /// NewMessagerMap creates a new MessagerMap based on the registered messagers. + /// + private Dictionary NewMessagerMap() + { + var messagerMap = new Dictionary(); + foreach (var kv in Registry.Registrar) + { + if (_options?.Filter?.Invoke(kv.Key) ?? true) + { + messagerMap[kv.Key] = kv.Value(); + } + } + return messagerMap; + } + } + + /// + /// Registry manages the registration of all messager generators. + /// + public class Registry + { + internal static readonly Dictionary> Registrar = []; + + /// + /// Register registers a messager generator for type T. + /// + public static void Register() where T : Messager, IMessagerName, new() => Registrar[T.Name()] = () => new T(); + + /// + /// Init registers all generated messagers. + /// + public static void Init() + { +{{ range . }} Register<{{ . }}>(); +{{ end }} } + } +} diff --git a/cmd/protoc-gen-csharp-tableau-loader/helper/helper.go b/cmd/protoc-gen-csharp-tableau-loader/helper/helper.go new file mode 100644 index 00000000..e3d2cede --- /dev/null +++ b/cmd/protoc-gen-csharp-tableau-loader/helper/helper.go @@ -0,0 +1,278 @@ +package helper + +import ( + "fmt" + "strings" + + "github.com/iancoleman/strcase" + "github.com/tableauio/tableau/proto/tableaupb" + "google.golang.org/protobuf/compiler/protogen" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/reflect/protoreflect" + "google.golang.org/protobuf/types/descriptorpb" +) + +func GenerateFileHeader(gen *protogen.Plugin, file *protogen.File, g *protogen.GeneratedFile, version string) { + g.P("// ") + g.P("// Code generated by protoc-gen-csharp-tableau-loader. DO NOT EDIT.") + g.P("// versions:") + g.P("// - protoc-gen-csharp-tableau-loader v", version) + g.P("// - protoc ", protocVersion(gen)) + if file != nil { + if file.Proto.GetOptions().GetDeprecated() { + g.P("// ", file.Desc.Path(), " is a deprecated file.") + } else { + g.P("// source: ", file.Desc.Path()) + } + } + g.P("// ") + g.P("#nullable enable") +} + +func protocVersion(gen *protogen.Plugin) string { + v := gen.Request.GetCompilerVersion() + if v == nil { + return "(unknown)" + } + var suffix string + if s := v.GetSuffix(); s != "" { + suffix = "-" + s + } + return fmt.Sprintf("v%d.%d.%d%s", v.GetMajor(), v.GetMinor(), v.GetPatch(), suffix) +} + +func ParseIndexFieldName(fd protoreflect.FieldDescriptor) string { + return ParseCsharpPropertyName(fd) +} + +// ParseCsharpPropertyName converts a protobuf field descriptor to the corresponding +// C# property name, matching the behavior of protoc's C# code generator. +// When the PascalCase field name collides with its containing type name, +// "Types", or "Descriptor", a trailing underscore is appended to avoid conflicts. +// See: protobuf/src/google/protobuf/compiler/csharp/csharp_helpers.cc GetPropertyName() +func ParseCsharpPropertyName(fd protoreflect.FieldDescriptor) string { + propertyName := strcase.ToCamel(string(fd.Name())) + containingName := string(fd.Parent().(protoreflect.MessageDescriptor).Name()) + if propertyName == containingName || propertyName == "Types" || propertyName == "Descriptor" { + propertyName += "_" + } + return propertyName +} + +func ParseIndexFieldNameAsKeyStructFieldName(fd protoreflect.FieldDescriptor) string { + if fd.IsList() { + opts := fd.Options().(*descriptorpb.FieldOptions) + fdOpts := proto.GetExtension(opts, tableaupb.E_Field).(*tableaupb.FieldOptions) + return strcase.ToCamel(fdOpts.GetName()) + } + // NOTE: Do not use ParseCsharpPropertyName here, because key struct field names + // are defined in our own code (not protobuf-generated), so they are not subject + // to protoc's property name conflict resolution (appending "_" suffix). + return strcase.ToCamel(string(fd.Name())) +} + +func ParseIndexFieldNameAsFuncParam(fd protoreflect.FieldDescriptor) string { + return escapeIdentifier(strcase.ToLowerCamel(ParseIndexFieldNameAsKeyStructFieldName(fd))) +} + +// ParseCsharpType converts a FieldDescriptor to C# type string. +func ParseCsharpType(fd protoreflect.FieldDescriptor) string { + switch fd.Kind() { + case protoreflect.BoolKind: + return "bool" + case protoreflect.EnumKind: + fullname := string(fd.Enum().FullName()) + seps := strings.Split(fullname, ".") + seps[0] = strcase.ToCamel(seps[0]) + for i := 2; i < len(seps); i++ { + seps[i] = "Types." + seps[i] + } + return strings.Join(seps, ".") + case protoreflect.Int32Kind, protoreflect.Sint32Kind, protoreflect.Sfixed32Kind: + return "int" + case protoreflect.Uint32Kind, protoreflect.Fixed32Kind: + return "uint" + case protoreflect.Int64Kind, protoreflect.Sint64Kind, protoreflect.Sfixed64Kind: + return "long" + case protoreflect.Uint64Kind, protoreflect.Fixed64Kind: + return "ulong" + case protoreflect.FloatKind: + return "float" + case protoreflect.DoubleKind: + return "double" + case protoreflect.StringKind, protoreflect.BytesKind: + return "string" + case protoreflect.MessageKind: + return ParseCsharpClassType(fd.Message()) + // case protoreflect.GroupKind: + // return "group" + default: + return fmt.Sprintf("", fd.Kind()) + } +} + +func ParseCsharpClassType(md protoreflect.MessageDescriptor) string { + ns := ParseCsharpNamespace(md.ParentFile()) + name := getNestedName(md) + if ns != "" { + return ns + "." + name + } + return name +} + +func getNestedName(md protoreflect.MessageDescriptor) string { + parent, ok := md.Parent().(protoreflect.MessageDescriptor) + if ok { + return getNestedName(parent) + ".Types." + string(md.Name()) + } + return string(md.Name()) +} + +func ParseCsharpNamespace(fd protoreflect.FileDescriptor) string { + if opts, ok := fd.Options().(*descriptorpb.FileOptions); ok && opts != nil { + if ns := opts.GetCsharpNamespace(); ns != "" { + return ns + } + } + return strcase.ToCamel(string(fd.Package())) +} + +// ParseOrderedIndexKeyType converts a FieldDescriptor to its treemap key type. +// fd must be an ordered type, or a message which can be converted to an ordered type. +func ParseOrderedIndexKeyType(fd protoreflect.FieldDescriptor) string { + switch fd.Kind() { + case protoreflect.Int32Kind, protoreflect.Sint32Kind, protoreflect.Sfixed32Kind: + return "int" + case protoreflect.Uint32Kind, protoreflect.Fixed32Kind: + return "uint" + case protoreflect.Int64Kind, protoreflect.Sint64Kind, protoreflect.Sfixed64Kind: + return "long" + case protoreflect.Uint64Kind, protoreflect.Fixed64Kind: + return "ulong" + case protoreflect.FloatKind: + return "float" + case protoreflect.DoubleKind: + return "double" + case protoreflect.StringKind: + return "string" + case protoreflect.EnumKind: + fullname := string(fd.Enum().FullName()) + seps := strings.Split(fullname, ".") + seps[0] = strcase.ToCamel(seps[0]) + for i := 2; i < len(seps); i++ { + seps[i] = "Types." + seps[i] + } + return strings.Join(seps, ".") + case protoreflect.MessageKind: + switch fd.Message().FullName() { + case "google.protobuf.Timestamp", "google.protobuf.Duration": + return "long" + default: + } + fallthrough + default: + panic(fmt.Sprintf("unsupported kind: %d", fd.Kind())) + } +} + +func ParseMapFieldNameAsKeyStructFieldName(fd protoreflect.FieldDescriptor) string { + opts := fd.Options().(*descriptorpb.FieldOptions) + fdOpts := proto.GetExtension(opts, tableaupb.E_Field).(*tableaupb.FieldOptions) + name := fdOpts.GetKey() + if fd.MapValue().Kind() == protoreflect.MessageKind { + valueFd := fd.MapValue().Message().Fields().Get(0) + name = string(valueFd.Name()) + } + return strcase.ToCamel(name) +} + +func ParseMapFieldNameAsFuncParam(fd protoreflect.FieldDescriptor) string { + fieldName := ParseMapFieldNameAsKeyStructFieldName(fd) + if fieldName == "" { + return fieldName + } + return escapeIdentifier(fieldName) +} + +func ParseMapKeyType(fd protoreflect.FieldDescriptor) string { + return ParseCsharpType(fd) +} + +func ParseMapValueType(fd protoreflect.FieldDescriptor) string { + return ParseCsharpType(fd.MapValue()) +} + +func GetTypeEmptyValue(fd protoreflect.FieldDescriptor) string { + switch fd.Kind() { + case protoreflect.BoolKind: + return "false" + case protoreflect.Int32Kind, protoreflect.Sint32Kind, protoreflect.Sfixed32Kind, + protoreflect.Uint32Kind, protoreflect.Fixed32Kind, + protoreflect.Int64Kind, protoreflect.Sint64Kind, protoreflect.Sfixed64Kind, + protoreflect.Uint64Kind, protoreflect.Fixed64Kind, protoreflect.EnumKind: + return "0" + case protoreflect.FloatKind, protoreflect.DoubleKind: + return "0.0" + case protoreflect.StringKind: + return `""` + case protoreflect.BytesKind, protoreflect.MessageKind: + return "null" + // case protoreflect.GroupKind: + // return "group" + default: + return fmt.Sprintf("", fd.Kind()) + } +} + +func ParseLeveledMapPrefix(md protoreflect.MessageDescriptor, mapFd protoreflect.FieldDescriptor) string { + if mapFd.MapValue().Kind() == protoreflect.MessageKind { + localMsgProtoName := strings.TrimPrefix(string(mapFd.MapValue().Message().FullName()), string(md.FullName())+".") + return strings.ReplaceAll(localMsgProtoName, ".", "_") + } + return mapFd.MapValue().Kind().String() +} + +type MapKey struct { + Type string + Name string + FieldName string // multi-colunm index only + Fd protoreflect.FieldDescriptor // the map field descriptor this key belongs to +} + +type MapKeySlice []MapKey + +func (s MapKeySlice) AddMapKey(newKey MapKey) MapKeySlice { + if newKey.Name == "" { + newKey.Name = fmt.Sprintf("key%d", len(s)+1) + } + for _, key := range s { + if key.Name == newKey.Name { + // rewrite to avoid name confict + newKey.Name = fmt.Sprintf("%s%d", newKey.Name, len(s)+1) + break + } + } + return append(s, newKey) +} + +// GenGetParams generates function parameters, which are the names listed in the function's definition. +func (s MapKeySlice) GenGetParams() string { + return s.GenCustom(func(key MapKey) string { return key.Type + " " + key.Name }, ", ") +} + +// GenGetArguments generates function arguments, which are the real values passed to the function. +func (s MapKeySlice) GenGetArguments() string { + return s.GenCustom(func(key MapKey) string { return key.Name }, ", ") +} + +func (s MapKeySlice) GenCustom(fn func(MapKey) string, sep string) string { + var params []string + for _, key := range s { + params = append(params, fn(key)) + } + return strings.Join(params, sep) +} + +func Indent(depth int) string { + return strings.Repeat(" ", depth) +} diff --git a/cmd/protoc-gen-csharp-tableau-loader/helper/keyword.go b/cmd/protoc-gen-csharp-tableau-loader/helper/keyword.go new file mode 100644 index 00000000..7599c731 --- /dev/null +++ b/cmd/protoc-gen-csharp-tableau-loader/helper/keyword.go @@ -0,0 +1,117 @@ +package helper + +import ( + "strings" + "unicode" + + "github.com/iancoleman/strcase" +) + +var csharpKeywords map[string]bool + +func escapeIdentifier(str string) string { + // Filter invalid runes + var result strings.Builder + for _, r := range str { + if unicode.IsLetter(r) || unicode.IsNumber(r) || r == '_' { + result.WriteRune(r) + } + } + str = result.String() + // To camel case + str = strcase.ToLowerCamel(str) + // Csharp variables must not start with digits + if len(str) != 0 && unicode.IsDigit(rune(str[0])) { + str = "_" + str + } + // Avoid csharp keywords + if _, ok := csharpKeywords[str]; ok { + return str + "_" + } + return str +} + +// Ref: +// +// https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords +func init() { + csharpKeywords = map[string]bool{ + "abstract": true, + "as": true, + "base": true, + "bool": true, + "break": true, + "byte": true, + "case": true, + "catch": true, + "char": true, + "checked": true, + "class": true, + "const": true, + "continue": true, + "decimal": true, + "default": true, + "delegate": true, + "do": true, + "double": true, + "else": true, + "enum": true, + "event": true, + "explicit": true, + "extern": true, + "false": true, + "finally": true, + "fixed": true, + "float": true, + "for": true, + "foreach": true, + "goto": true, + "if": true, + "implicit": true, + "in": true, + "int": true, + "interface": true, + "internal": true, + "is": true, + "lock": true, + "long": true, + "namespace": true, + "new": true, + "null": true, + "object": true, + "operator": true, + "out": true, + "override": true, + "params": true, + "private": true, + "protected": true, + "public": true, + "readonly": true, + "ref": true, + "return": true, + "sbyte": true, + "sealed": true, + "short": true, + "sizeof": true, + "stackalloc": true, + "static": true, + "string": true, + "struct": true, + "switch": true, + "this": true, + "throw": true, + "true": true, + "try": true, + "typeof": true, + "uint": true, + "ulong": true, + "unchecked": true, + "unsafe": true, + "ushort": true, + "using": true, + "virtual": true, + "void": true, + "volatile": true, + "while": true, + } +} diff --git a/cmd/protoc-gen-csharp-tableau-loader/hub.go b/cmd/protoc-gen-csharp-tableau-loader/hub.go new file mode 100644 index 00000000..da474435 --- /dev/null +++ b/cmd/protoc-gen-csharp-tableau-loader/hub.go @@ -0,0 +1,21 @@ +package main + +import ( + "text/template" + + "github.com/tableauio/loader/cmd/protoc-gen-csharp-tableau-loader/helper" + "github.com/tableauio/loader/internal/extensions" + "google.golang.org/protobuf/compiler/protogen" +) + +var tpl = template.Must(template.New("").ParseFS(efs, "embed/templates/*")) + +// generateHub generates related hub files. +func generateHub(gen *protogen.Plugin) { + filename := "Hub." + extensions.PC + ".cs" + g := gen.NewGeneratedFile(filename, "") + helper.GenerateFileHeader(gen, nil, g, version) + if err := tpl.Lookup(filename+".tpl").Execute(g, messagers); err != nil { + panic(err) + } +} diff --git a/cmd/protoc-gen-csharp-tableau-loader/indexes/generator.go b/cmd/protoc-gen-csharp-tableau-loader/indexes/generator.go new file mode 100644 index 00000000..29f84787 --- /dev/null +++ b/cmd/protoc-gen-csharp-tableau-loader/indexes/generator.go @@ -0,0 +1,154 @@ +package indexes + +import ( + "fmt" + + "github.com/tableauio/loader/cmd/protoc-gen-csharp-tableau-loader/helper" + "github.com/tableauio/loader/internal/index" + "google.golang.org/protobuf/compiler/protogen" + "google.golang.org/protobuf/reflect/protoreflect" +) + +type Generator struct { + g *protogen.GeneratedFile + descriptor *index.IndexDescriptor + message *protogen.Message + + // level message + keys helper.MapKeySlice +} + +func NewGenerator(g *protogen.GeneratedFile, descriptor *index.IndexDescriptor, message *protogen.Message) *Generator { + generator := &Generator{ + g: g, + descriptor: descriptor, + message: message, + } + generator.initLevelMessage() + return generator +} + +func (x *Generator) initLevelMessage() { + for lm := x.descriptor.LevelMessage; lm != nil; lm = lm.NextLevel { + if fd := lm.FD; fd != nil && fd.IsMap() { + // Only collect map keys/fds when a deeper level has an index or ordered index, + // because these keys are used solely for building upper-level (leveled) containers. + if !lm.NextLevel.NeedGenAnyIndex() { + break + } + x.keys = x.keys.AddMapKey(helper.MapKey{ + Type: helper.ParseMapKeyType(fd.MapKey()), + Name: helper.ParseMapFieldNameAsFuncParam(fd), + FieldName: helper.ParseMapFieldNameAsKeyStructFieldName(fd), + Fd: fd, + }) + } + } +} + +func (x *Generator) NeedGenerate() bool { + return x.needGenerateIndex() || x.needGenerateOrderedIndex() +} + +func (x *Generator) levelKeyType(mapFd protoreflect.FieldDescriptor) string { + return fmt.Sprintf("LevelIndex_%sKey", helper.ParseLeveledMapPrefix(x.message.Desc, mapFd)) +} + +func (x *Generator) mapValueType(index *index.LevelIndex) string { + return helper.ParseCsharpClassType(index.MD) +} + +func (x *Generator) fieldGetter(fd protoreflect.FieldDescriptor) string { + return fmt.Sprintf(".%s", helper.ParseIndexFieldName(fd)) +} + +func (x *Generator) parseKeyFieldNameAndSuffix(field *index.LevelField) (string, string) { + var fieldName, suffix string + needEmptyValue := len(field.LeveledFDList) > 1 + for i, leveledFd := range field.LeveledFDList { + if i != 0 { + fieldName += "?" + } + fieldName += x.fieldGetter(leveledFd) + if i == len(field.LeveledFDList)-1 && leveledFd.Message() != nil { + switch leveledFd.Message().FullName() { + case "google.protobuf.Timestamp", "google.protobuf.Duration": + suffix = "?.Seconds ?? 0" + needEmptyValue = false + default: + } + } + } + if field.FD.IsList() { + fieldName += " ?? Enumerable.Empty<" + helper.ParseCsharpType(field.FD) + ">()" + } else if needEmptyValue { + fieldName += " ?? " + helper.GetTypeEmptyValue(field.FD) + } + return fieldName, suffix +} + +func (x *Generator) GenIndexTypeDef() { + if !x.NeedGenerate() { + return + } + // Generate LevelIndex key structs for intermediate map levels. + // + // x.keys holds one entry per map level whose next level still needs an + // index (populated by initLevelMessage). For a 3-level map keyed by + // (k1, k2, k3) with an index at the deepest level, x.keys = [k1, k2, k3]. + // + // Level containers at depth 1 are keyed by a single scalar (k1), so no + // composite key struct is needed. The deepest level (depth = len(keys)) + // also does not need one, because its full key combination (all keys) + // is already represented by the index's own key struct generated + // separately. Only intermediate depths (2 ≤ depth < len(keys)) require + // a LevelIndex struct that bundles all ancestor keys up to that depth: + // + // keys = [k1, k2, k3] → struct for depth 2: {k1, k2} + // keys = [k1, k2, k3, k4] → struct for depth 2: {k1, k2} + // struct for depth 3: {k1, k2, k3} + // + // The loop starts at i=2 (depth 2) and creates a struct from keys[:i]. + // It runs len(x.keys)-2 times (0 times when len ≤ 2). + for i := 2; i < len(x.keys); i++ { + if i == 2 { + x.g.P() + x.g.P(helper.Indent(2), "// LevelIndex keys.") + } + fd := x.keys[i-1].Fd + keyType := x.levelKeyType(fd) + x.g.P(helper.Indent(2), "public readonly struct ", keyType, " : IEquatable<", keyType, ">") + x.g.P(helper.Indent(2), "{") + keys := x.keys[:i] + for _, key := range keys { + x.g.P(helper.Indent(3), "public ", key.Type, " ", key.FieldName, " { get; }") + } + x.g.P() + x.g.P(helper.Indent(3), "public ", keyType, "(", keys.GenGetParams(), ")") + x.g.P(helper.Indent(3), "{") + for _, key := range keys { + x.g.P(helper.Indent(4), key.FieldName, " = ", key.Name, ";") + } + x.g.P(helper.Indent(3), "}") + x.g.P() + x.g.P(helper.Indent(3), "public bool Equals(", keyType, " other) =>") + x.g.P(helper.Indent(4), "(", keys.GenCustom(func(key helper.MapKey) string { return key.FieldName }, ", "), ").Equals((", keys.GenCustom(func(key helper.MapKey) string { return "other." + key.FieldName }, ", "), "));") + x.g.P() + x.g.P(helper.Indent(3), "public override int GetHashCode() =>") + x.g.P(helper.Indent(4), "(", keys.GenCustom(func(key helper.MapKey) string { return key.FieldName }, ", "), ").GetHashCode();") + x.g.P(helper.Indent(2), "}") + x.g.P() + } + x.genIndexTypeDef() + x.genOrderedIndexTypeDef() +} + +func (x *Generator) GenIndexLoader() { + x.genIndexLoader() + x.genOrderedIndexLoader() +} + +func (x *Generator) GenIndexFinders() { + x.genIndexFinders() + x.genOrderedIndexFinders() +} diff --git a/cmd/protoc-gen-csharp-tableau-loader/indexes/index.go b/cmd/protoc-gen-csharp-tableau-loader/indexes/index.go new file mode 100644 index 00000000..b0f9d496 --- /dev/null +++ b/cmd/protoc-gen-csharp-tableau-loader/indexes/index.go @@ -0,0 +1,343 @@ +package indexes + +import ( + "fmt" + "strings" + "sync" + + "github.com/iancoleman/strcase" + "github.com/tableauio/loader/cmd/protoc-gen-csharp-tableau-loader/helper" + "github.com/tableauio/loader/internal/index" + "github.com/tableauio/loader/internal/loadutil" + "github.com/tableauio/loader/internal/options" +) + +func (x *Generator) needGenerateIndex() bool { + return options.NeedGenIndex(x.message.Desc, options.LangCS) +} + +func (x *Generator) indexMapType(index *index.LevelIndex) string { + return fmt.Sprintf("Index_%sMap", index.Name()) +} + +func (x *Generator) indexMapKeyType(index *index.LevelIndex) string { + if len(index.ColFields) == 1 { + // single-column index + field := index.ColFields[0] // just take first field + return helper.ParseCsharpType(field.FD) + } else { + // multi-column index + return fmt.Sprintf("Index_%sKey", index.Name()) + } +} + +func (x *Generator) indexContainerName(index *index.LevelIndex, i int) string { + if i == 0 { + return fmt.Sprintf("_index%sMap", strcase.ToCamel(index.Name())) + } + return fmt.Sprintf("_index%sMap%d", strcase.ToCamel(index.Name()), i) +} + +func (x *Generator) indexKeys(index *index.LevelIndex) helper.MapKeySlice { + var keys helper.MapKeySlice + for _, field := range index.ColFields { + keys = keys.AddMapKey(helper.MapKey{ + Type: helper.ParseCsharpType(field.FD), + Name: helper.ParseIndexFieldNameAsFuncParam(field.FD), + FieldName: helper.ParseIndexFieldNameAsKeyStructFieldName(field.FD), + }) + } + return keys +} + +func (x *Generator) genIndexTypeDef() { + if !x.needGenerateIndex() { + return + } + var once sync.Once + for lm := x.descriptor.LevelMessage; lm != nil; lm = lm.NextLevel { + for _, index := range lm.Indexes { + once.Do(func() { x.g.P(helper.Indent(2), "// Index types.") }) + x.g.P(helper.Indent(2), "// Index: ", index.Index) + mapType := x.indexMapType(index) + keyType := x.indexMapKeyType(index) + valueType := x.mapValueType(index) + keys := x.indexKeys(index) + if len(index.ColFields) != 1 { + // Generate key struct + x.g.P(helper.Indent(2), "public readonly struct ", keyType, " : IEquatable<", keyType, ">") + x.g.P(helper.Indent(2), "{") + for _, key := range keys { + x.g.P(helper.Indent(3), "public ", key.Type, " ", key.FieldName, " { get; }") + } + x.g.P() + x.g.P(helper.Indent(3), "public ", keyType, "(", keys.GenGetParams(), ")") + x.g.P(helper.Indent(3), "{") + for _, key := range keys { + x.g.P(helper.Indent(4), key.FieldName, " = ", key.Name, ";") + } + x.g.P(helper.Indent(3), "}") + x.g.P() + x.g.P(helper.Indent(3), "public bool Equals(", keyType, " other) =>") + x.g.P(helper.Indent(4), "(", keys.GenCustom(func(key helper.MapKey) string { return key.FieldName }, ", "), ").Equals((", keys.GenCustom(func(key helper.MapKey) string { return "other." + key.FieldName }, ", "), "));") + x.g.P() + x.g.P(helper.Indent(3), "public override int GetHashCode() =>") + x.g.P(helper.Indent(4), "(", keys.GenCustom(func(key helper.MapKey) string { return key.FieldName }, ", "), ").GetHashCode();") + x.g.P(helper.Indent(2), "}") + x.g.P() + } + x.g.P(helper.Indent(2), "public class ", mapType, " : Dictionary<", keyType, ", List<", valueType, ">>;") + x.g.P() + + x.g.P(helper.Indent(2), "private ", mapType, " ", x.indexContainerName(index, 0), " = [];") + x.g.P() + for i := 1; i < lm.MapDepth; i++ { + if i == 1 { + x.g.P(helper.Indent(2), "private Dictionary<", x.keys[0].Type, ", ", mapType, "> ", x.indexContainerName(index, i), " = [];") + } else { + levelIndexKeyType := x.levelKeyType(x.keys[i-1].Fd) + x.g.P(helper.Indent(2), "private Dictionary<", levelIndexKeyType, ", ", mapType, "> ", x.indexContainerName(index, i), " = [];") + } + x.g.P() + } + } + } +} + +func (x *Generator) genIndexLoader() { + if !x.needGenerateIndex() { + return + } + defer x.genIndexSorter() + x.g.P(helper.Indent(3), "// Index init.") + for lm := x.descriptor.LevelMessage; lm != nil; lm = lm.NextLevel { + for _, index := range lm.Indexes { + x.g.P(helper.Indent(3), x.indexContainerName(index, 0), ".Clear();") + for i := 1; i < lm.MapDepth; i++ { + x.g.P(helper.Indent(3), x.indexContainerName(index, i), ".Clear();") + } + } + } + parentDataName := "_data" + for lm := x.descriptor.LevelMessage; lm != nil; lm = lm.NextLevel { + itemName := fmt.Sprintf("item%d", lm.Depth) + if !lm.NeedGenIndex() { + break + } + x.g.P(helper.Indent(lm.Depth+2), "foreach (var ", itemName, " in ", parentDataName, x.fieldGetter(lm.FD), ")") + x.g.P(helper.Indent(lm.Depth+2), "{") + parentDataName = itemName + if lm.FD.IsMap() { + if lm.NeedMapKeyForIndex() { + x.g.P(helper.Indent(lm.Depth+3), "var k", lm.MapDepth, " = ", itemName, ".Key;") + } + parentDataName = itemName + ".Value" + } + defer x.g.P(helper.Indent(lm.Depth+2), "}") + for _, index := range lm.Indexes { + x.genOneCsharpIndexLoader(lm, index, parentDataName) + } + } +} + +func (x *Generator) genOneCsharpIndexLoader(lm *index.LevelMessage, index *index.LevelIndex, parentDataName string) { + ident := lm.Depth + 1 + x.g.P(helper.Indent(ident+2), "{") + x.g.P(helper.Indent(ident+3), "// Index: ", index.Index) + if len(index.ColFields) == 1 { + // single-column index + field := index.ColFields[0] // just take the first field + fieldName, _ := x.parseKeyFieldNameAndSuffix(field) + if field.FD.IsList() { + itemName := fmt.Sprintf("item%d", lm.MapDepth+1) + x.g.P(helper.Indent(ident+3), "foreach (var ", itemName, " in ", parentDataName, fieldName, ")") + x.g.P(helper.Indent(ident+3), "{") + key := itemName + x.genLoader(lm, index, ident+4, key, parentDataName) + x.g.P(helper.Indent(ident+3), "}") + } else { + key := parentDataName + fieldName + x.g.P(helper.Indent(ident+3), "var key = ", key, ";") + x.genLoader(lm, index, ident+3, "key", parentDataName) + } + } else { + // multi-column index + x.generateOneMulticolumnIndex(lm, index, ident+2, parentDataName, nil) + } + x.g.P(helper.Indent(ident+2), "}") +} + +func (x *Generator) generateOneMulticolumnIndex(lm *index.LevelMessage, index *index.LevelIndex, ident int, parentDataName string, keys helper.MapKeySlice) { + cursor := len(keys) + if cursor >= len(index.ColFields) { + keyType := x.indexMapKeyType(index) + x.g.P(helper.Indent(ident+1), "var key = new ", keyType, "(", keys.GenGetArguments(), ");") + x.genLoader(lm, index, ident+1, "key", parentDataName) + return + } + field := index.ColFields[cursor] + fieldName, _ := x.parseKeyFieldNameAndSuffix(field) + if field.FD.IsList() { + itemName := fmt.Sprintf("indexItem%d", cursor) + x.g.P(helper.Indent(ident+1), "foreach (var ", itemName, " in ", parentDataName, fieldName, ")") + x.g.P(helper.Indent(ident+1), "{") + key := itemName + keys = keys.AddMapKey(helper.MapKey{Name: key}) + x.generateOneMulticolumnIndex(lm, index, ident+1, parentDataName, keys) + x.g.P(helper.Indent(ident+1), "}") + } else { + key := parentDataName + fieldName + keys = keys.AddMapKey(helper.MapKey{Name: key}) + x.generateOneMulticolumnIndex(lm, index, ident, parentDataName, keys) + } +} + +func (x *Generator) genLoader(lm *index.LevelMessage, index *index.LevelIndex, ident int, key, parentDataName string) { + x.g.P(helper.Indent(ident), "{") + x.g.P(helper.Indent(ident+1), "var list = ", x.indexContainerName(index, 0), ".TryGetValue(", key, ", out var existingList) ?") + x.g.P(helper.Indent(ident+1), "existingList : ", x.indexContainerName(index, 0), "[", key, "] = [];") + x.g.P(helper.Indent(ident+1), "list.Add(", parentDataName, ");") + x.g.P(helper.Indent(ident), "}") + for i := 1; i < lm.MapDepth; i++ { + x.g.P(helper.Indent(ident), "{") + if i == 1 { + x.g.P(helper.Indent(ident+1), "var map = ", x.indexContainerName(index, i), ".TryGetValue(k1, out var existingMap) ?") + x.g.P(helper.Indent(ident+1), "existingMap : ", x.indexContainerName(index, i), "[k1] = [];") + x.g.P(helper.Indent(ident+1), "var list = map.TryGetValue(", key, ", out var existingList) ?") + x.g.P(helper.Indent(ident+1), "existingList : map[", key, "] = [];") + x.g.P(helper.Indent(ident+1), "list.Add(", parentDataName, ");") + } else { + var fields []string + for j := 1; j <= i; j++ { + fields = append(fields, fmt.Sprintf("k%d", j)) + } + levelIndexKeyType := x.levelKeyType(x.keys[i-1].Fd) + x.g.P(helper.Indent(ident+1), "var mapKey = new ", levelIndexKeyType, "(", strings.Join(fields, ", "), ");") + x.g.P(helper.Indent(ident+1), "var map = ", x.indexContainerName(index, i), ".TryGetValue(mapKey, out var existingMap) ?") + x.g.P(helper.Indent(ident+1), "existingMap : ", x.indexContainerName(index, i), "[mapKey] = [];") + x.g.P(helper.Indent(ident+1), "var list = map.TryGetValue(", key, ", out var existingList) ?") + x.g.P(helper.Indent(ident+1), "existingList : map[", key, "] = [];") + x.g.P(helper.Indent(ident+1), "list.Add(", parentDataName, ");") + } + x.g.P(helper.Indent(ident), "}") + } +} + +func (x *Generator) genIndexSorter() { + for lm := x.descriptor.LevelMessage; lm != nil; lm = lm.NextLevel { + for _, index := range lm.Indexes { + if len(index.SortedColFields) != 0 { + valueType := x.mapValueType(index) + x.g.P(helper.Indent(3), "// Index(sort): ", index.Index) + indexContainerName := x.indexContainerName(index, 0) + sorter := strings.TrimPrefix(indexContainerName, "_") + "Comparison" + x.g.P(helper.Indent(3), "Comparison<", valueType, "> ", sorter, " = (a, b) =>") + var keys helper.MapKeySlice + for _, field := range index.SortedColFields { + fieldName, _ := x.parseKeyFieldNameAndSuffix(field) + keys = keys.AddMapKey(helper.MapKey{Name: fieldName}) + } + x.g.P(helper.Indent(4), "(", keys.GenCustom(func(key helper.MapKey) string { return "a" + key.Name }, ", "), ").CompareTo((", keys.GenCustom(func(key helper.MapKey) string { return "b" + key.Name }, ", "), "));") + x.g.P(helper.Indent(3), "foreach (var itemList in ", indexContainerName, ".Values)") + x.g.P(helper.Indent(3), "{") + x.g.P(helper.Indent(4), "itemList.Sort(", sorter, ");") + x.g.P(helper.Indent(3), "}") + // Iterate all leveled containers. + for i := 1; i < lm.MapDepth; i++ { + x.g.P(helper.Indent(3), "foreach (var itemDict in ", x.indexContainerName(index, i), ".Values)") + x.g.P(helper.Indent(3), "{") + x.g.P(helper.Indent(4), "foreach (var itemList in itemDict.Values)") + x.g.P(helper.Indent(4), "{") + x.g.P(helper.Indent(5), "itemList.Sort(", sorter, ");") + x.g.P(helper.Indent(4), "}") + x.g.P(helper.Indent(3), "}") + } + } + } + } +} + +func (x *Generator) genIndexFinders() { + if !x.needGenerateIndex() { + return + } + for lm := x.descriptor.LevelMessage; lm != nil; lm = lm.NextLevel { + for _, index := range lm.Indexes { + mapType := x.indexMapType(index) + mapValueType := x.mapValueType(index) + indexContainerName := x.indexContainerName(index, 0) + + x.g.P() + x.g.P(helper.Indent(2), "// Index: ", index.Index) + x.g.P() + x.g.P(helper.Indent(2), "/// ") + x.g.P(helper.Indent(2), "/// Find", index.Name(), "Map finds the index: key(", index.Index, ") to value(", mapValueType, ") map.") + x.g.P(helper.Indent(2), "/// One key may correspond to multiple values, which are represented by a list.") + x.g.P(helper.Indent(2), "/// ") + x.g.P(helper.Indent(2), "public ref readonly ", mapType, " Find", index.Name(), "Map() => ref ", indexContainerName, ";") + x.g.P() + + keyType := x.indexMapKeyType(index) + keys := x.indexKeys(index) + params := keys.GenGetParams() + args := keys.GenGetArguments() + x.g.P(helper.Indent(2), "/// ") + x.g.P(helper.Indent(2), "/// Find", index.Name(), " finds a list of all values of the given key(s).") + x.g.P(helper.Indent(2), "/// ") + x.g.P(helper.Indent(2), "public List<", mapValueType, ">? Find", index.Name(), "(", params, ") =>") + if len(index.ColFields) == 1 { + x.g.P(helper.Indent(3), indexContainerName, ".TryGetValue(", args, ", out var value) ? value : null;") + } else { + x.g.P(helper.Indent(3), indexContainerName, ".TryGetValue(new ", keyType, "(", args, "), out var value) ? value : null;") + } + x.g.P() + + x.g.P(helper.Indent(2), "/// ") + x.g.P(helper.Indent(2), "/// FindFirst", index.Name(), " finds the first value of the given key(s),") + x.g.P(helper.Indent(2), "/// or null if no value found.") + x.g.P(helper.Indent(2), "/// ") + x.g.P(helper.Indent(2), "public ", mapValueType, "? FindFirst", index.Name(), "(", params, ") =>") + x.g.P(helper.Indent(3), "Find", index.Name(), "(", args, ")?.FirstOrDefault();") + + for i := 1; i < lm.MapDepth; i++ { + indexContainerName := x.indexContainerName(index, i) + partKeys := x.keys[:i] + partParams := partKeys.GenGetParams() + partArgs := partKeys.GenGetArguments() + x.g.P() + x.g.P(helper.Indent(2), "/// ") + x.g.P(helper.Indent(2), "/// Find", index.Name(), "Map", i, " finds the index: key(", index.Index, ") to value(", mapValueType, "),") + x.g.P(helper.Indent(2), "/// which is the upper ", loadutil.Ordinal(i), "-level map specified by (", partArgs, ").") + x.g.P(helper.Indent(2), "/// One key may correspond to multiple values, which are represented by a list.") + x.g.P(helper.Indent(2), "/// ") + x.g.P(helper.Indent(2), "public ", mapType, "? Find", index.Name(), "Map", i, "(", partParams, ") =>") + if len(partKeys) == 1 { + x.g.P(helper.Indent(3), indexContainerName, ".TryGetValue(", partArgs, ", out var value) ? value : null;") + } else { + levelIndexKeyType := x.levelKeyType(x.keys[i-1].Fd) + x.g.P(helper.Indent(3), indexContainerName, ".TryGetValue(new ", levelIndexKeyType, "(", partArgs, "), out var value) ? value : null;") + } + + x.g.P() + x.g.P(helper.Indent(2), "/// ") + x.g.P(helper.Indent(2), "/// Find", index.Name(), i, " finds a list of all values of the given key(s) in the upper ", loadutil.Ordinal(i), "-level map") + x.g.P(helper.Indent(2), "/// specified by (", partArgs, ").") + x.g.P(helper.Indent(2), "/// ") + x.g.P(helper.Indent(2), "public List<", mapValueType, ">? Find", index.Name(), i, "(", partParams, ", ", params, ") =>") + if len(index.ColFields) == 1 { + x.g.P(helper.Indent(3), "Find", index.Name(), "Map", i, "(", partArgs, ")?.TryGetValue(", args, ", out var value) == true ? value : null;") + } else { + x.g.P(helper.Indent(3), "Find", index.Name(), "Map", i, "(", partArgs, ")?.TryGetValue(new ", keyType, "(", args, "), out var value) == true ? value : null;") + } + + x.g.P() + x.g.P(helper.Indent(2), "/// ") + x.g.P(helper.Indent(2), "/// FindFirst", index.Name(), i, " finds the first value of the given key(s) in the upper ", loadutil.Ordinal(i), "-level map") + x.g.P(helper.Indent(2), "/// specified by (", partArgs, "), or null if no value found.") + x.g.P(helper.Indent(2), "/// ") + x.g.P(helper.Indent(2), "public ", mapValueType, "? FindFirst", index.Name(), i, "(", partParams, ", ", params, ") =>") + x.g.P(helper.Indent(3), "Find", index.Name(), i, "(", partArgs, ", ", args, ")?.FirstOrDefault();") + } + } + } +} diff --git a/cmd/protoc-gen-csharp-tableau-loader/indexes/ordered_index.go b/cmd/protoc-gen-csharp-tableau-loader/indexes/ordered_index.go new file mode 100644 index 00000000..6a13f581 --- /dev/null +++ b/cmd/protoc-gen-csharp-tableau-loader/indexes/ordered_index.go @@ -0,0 +1,340 @@ +package indexes + +import ( + "fmt" + "strings" + "sync" + + "github.com/iancoleman/strcase" + "github.com/tableauio/loader/cmd/protoc-gen-csharp-tableau-loader/helper" + "github.com/tableauio/loader/internal/index" + "github.com/tableauio/loader/internal/loadutil" + "github.com/tableauio/loader/internal/options" +) + +func (x *Generator) needGenerateOrderedIndex() bool { + return options.NeedGenOrderedIndex(x.message.Desc, options.LangCS) +} + +func (x *Generator) orderedIndexMapType(index *index.LevelIndex) string { + return fmt.Sprintf("OrderedIndex_%sMap", index.Name()) +} + +func (x *Generator) orderedIndexMapKeyType(index *index.LevelIndex) string { + if len(index.ColFields) == 1 { + // single-column index + field := index.ColFields[0] // just take first field + return helper.ParseOrderedIndexKeyType(field.FD) + } else { + // multi-column index + return fmt.Sprintf("OrderedIndex_%sKey", index.Name()) + } +} + +func (x *Generator) orderedIndexContainerName(index *index.LevelIndex, i int) string { + if i == 0 { + return fmt.Sprintf("_orderedIndex%sMap", strcase.ToCamel(index.Name())) + } + return fmt.Sprintf("_orderedIndex%sMap%d", strcase.ToCamel(index.Name()), i) +} + +func (x *Generator) orderedIndexKeys(index *index.LevelIndex) helper.MapKeySlice { + var keys helper.MapKeySlice + for _, field := range index.ColFields { + keys = keys.AddMapKey(helper.MapKey{ + Type: helper.ParseOrderedIndexKeyType(field.FD), + Name: helper.ParseIndexFieldNameAsFuncParam(field.FD), + FieldName: helper.ParseIndexFieldNameAsKeyStructFieldName(field.FD), + }) + } + return keys +} + +func (x *Generator) genOrderedIndexTypeDef() { + if !x.needGenerateOrderedIndex() { + return + } + var once sync.Once + for lm := x.descriptor.LevelMessage; lm != nil; lm = lm.NextLevel { + for _, index := range lm.OrderedIndexes { + once.Do(func() { x.g.P(helper.Indent(2), "// OrderedIndex types.") }) + x.g.P(helper.Indent(2), "// OrderedIndex: ", index.Index) + mapType := x.orderedIndexMapType(index) + keyType := x.orderedIndexMapKeyType(index) + valueType := x.mapValueType(index) + keys := x.orderedIndexKeys(index) + if len(index.ColFields) != 1 { + // Generate key struct + x.g.P(helper.Indent(2), "public readonly struct ", keyType, " : IComparable<", keyType, ">") + x.g.P(helper.Indent(2), "{") + for _, key := range keys { + x.g.P(helper.Indent(3), "public ", key.Type, " ", key.FieldName, " { get; }") + } + x.g.P() + x.g.P(helper.Indent(3), "public ", keyType, "(", keys.GenGetParams(), ")") + x.g.P(helper.Indent(3), "{") + for _, key := range keys { + x.g.P(helper.Indent(4), key.FieldName, " = ", key.Name, ";") + } + x.g.P(helper.Indent(3), "}") + x.g.P() + x.g.P(helper.Indent(3), "public int CompareTo(", keyType, " other) =>") + x.g.P(helper.Indent(4), "(", keys.GenCustom(func(key helper.MapKey) string { return key.FieldName }, ", "), ").CompareTo((", keys.GenCustom(func(key helper.MapKey) string { return "other." + key.FieldName }, ", "), "));") + x.g.P(helper.Indent(2), "}") + x.g.P() + } + x.g.P(helper.Indent(2), "public class ", mapType, " : SortedDictionary<", keyType, ", List<", valueType, ">>;") + x.g.P() + + x.g.P(helper.Indent(2), "private ", mapType, " ", x.orderedIndexContainerName(index, 0), " = [];") + x.g.P() + for i := 1; i < lm.MapDepth; i++ { + if i == 1 { + x.g.P(helper.Indent(2), "private Dictionary<", x.keys[0].Type, ", ", mapType, "> ", x.orderedIndexContainerName(index, i), " = [];") + } else { + levelIndexKeyType := x.levelKeyType(x.keys[i-1].Fd) + x.g.P(helper.Indent(2), "private Dictionary<", levelIndexKeyType, ", ", mapType, "> ", x.orderedIndexContainerName(index, i), " = [];") + } + x.g.P() + } + } + } +} + +func (x *Generator) genOrderedIndexLoader() { + if !x.needGenerateOrderedIndex() { + return + } + defer x.genOrderedIndexSorter() + x.g.P(helper.Indent(3), "// OrderedIndex init.") + for lm := x.descriptor.LevelMessage; lm != nil; lm = lm.NextLevel { + for _, index := range lm.OrderedIndexes { + x.g.P(helper.Indent(3), x.orderedIndexContainerName(index, 0), ".Clear();") + for i := 1; i < lm.MapDepth; i++ { + x.g.P(helper.Indent(3), x.orderedIndexContainerName(index, i), ".Clear();") + } + } + } + parentDataName := "_data" + for lm := x.descriptor.LevelMessage; lm != nil; lm = lm.NextLevel { + itemName := fmt.Sprintf("item%d", lm.Depth) + if !lm.NeedGenOrderedIndex() { + break + } + x.g.P(helper.Indent(lm.Depth+2), "foreach (var ", itemName, " in ", parentDataName, x.fieldGetter(lm.FD), ")") + x.g.P(helper.Indent(lm.Depth+2), "{") + parentDataName = itemName + if lm.FD.IsMap() { + if lm.NeedMapKeyForOrderedIndex() { + x.g.P(helper.Indent(lm.Depth+3), "var k", lm.MapDepth, " = ", itemName, ".Key;") + } + parentDataName = itemName + ".Value" + } + defer x.g.P(helper.Indent(lm.Depth+2), "}") + for _, index := range lm.OrderedIndexes { + x.genOneCsharpOrderedIndexLoader(lm, index, parentDataName) + } + } +} + +func (x *Generator) genOneCsharpOrderedIndexLoader(lm *index.LevelMessage, index *index.LevelIndex, parentDataName string) { + ident := lm.Depth + 1 + x.g.P(helper.Indent(ident+2), "{") + x.g.P(helper.Indent(ident+3), "// OrderedIndex: ", index.Index) + if len(index.ColFields) == 1 { + // single-column index + field := index.ColFields[0] // just take the first field + fieldName, suffix := x.parseKeyFieldNameAndSuffix(field) + if field.FD.IsList() { + itemName := fmt.Sprintf("item%d", lm.MapDepth+1) + x.g.P(helper.Indent(ident+3), "foreach (var ", itemName, " in ", parentDataName, fieldName, ")") + x.g.P(helper.Indent(ident+3), "{") + x.g.P(helper.Indent(ident+4), "var key = ", itemName, suffix, ";") + x.genOrderedIndexLoaderCommon(lm, index, ident+4, "key", parentDataName) + x.g.P(helper.Indent(ident+3), "}") + } else { + key := parentDataName + fieldName + suffix + x.g.P(helper.Indent(ident+3), "var key = ", key, ";") + x.genOrderedIndexLoaderCommon(lm, index, ident+3, "key", parentDataName) + } + } else { + // multi-column index + x.generateOneMulticolumnOrderedIndex(lm, index, ident+2, parentDataName, nil) + } + x.g.P(helper.Indent(ident+2), "}") +} + +func (x *Generator) generateOneMulticolumnOrderedIndex(lm *index.LevelMessage, index *index.LevelIndex, ident int, parentDataName string, keys helper.MapKeySlice) { + cursor := len(keys) + if cursor >= len(index.ColFields) { + keyType := x.orderedIndexMapKeyType(index) + x.g.P(helper.Indent(ident+1), "var key = new ", keyType, "(", keys.GenGetArguments(), ");") + x.genOrderedIndexLoaderCommon(lm, index, ident+1, "key", parentDataName) + return + } + field := index.ColFields[cursor] + fieldName, suffix := x.parseKeyFieldNameAndSuffix(field) + if field.FD.IsList() { + itemName := fmt.Sprintf("indexItem%d", cursor) + x.g.P(helper.Indent(ident+1), "foreach (var ", itemName, " in ", parentDataName, fieldName, ")") + x.g.P(helper.Indent(ident+1), "{") + key := itemName + keys = keys.AddMapKey(helper.MapKey{Name: key}) + x.generateOneMulticolumnOrderedIndex(lm, index, ident+1, parentDataName, keys) + x.g.P(helper.Indent(ident+1), "}") + } else { + key := parentDataName + fieldName + suffix + keys = keys.AddMapKey(helper.MapKey{Name: key}) + x.generateOneMulticolumnOrderedIndex(lm, index, ident, parentDataName, keys) + } +} + +func (x *Generator) genOrderedIndexLoaderCommon(lm *index.LevelMessage, index *index.LevelIndex, ident int, key, parentDataName string) { + x.g.P(helper.Indent(ident), "{") + x.g.P(helper.Indent(ident+1), "var list = ", x.orderedIndexContainerName(index, 0), ".TryGetValue(", key, ", out var existingList) ?") + x.g.P(helper.Indent(ident+1), "existingList : ", x.orderedIndexContainerName(index, 0), "[", key, "] = [];") + x.g.P(helper.Indent(ident+1), "list.Add(", parentDataName, ");") + x.g.P(helper.Indent(ident), "}") + for i := 1; i < lm.MapDepth; i++ { + x.g.P(helper.Indent(ident), "{") + if i == 1 { + x.g.P(helper.Indent(ident+1), "var map = ", x.orderedIndexContainerName(index, i), ".TryGetValue(k1, out var existingMap) ?") + x.g.P(helper.Indent(ident+1), "existingMap : ", x.orderedIndexContainerName(index, i), "[k1] = [];") + x.g.P(helper.Indent(ident+1), "var list = map.TryGetValue(", key, ", out var existingList) ?") + x.g.P(helper.Indent(ident+1), "existingList : map[", key, "] = [];") + x.g.P(helper.Indent(ident+1), "list.Add(", parentDataName, ");") + } else { + var fields []string + for j := 1; j <= i; j++ { + fields = append(fields, fmt.Sprintf("k%d", j)) + } + levelIndexKeyType := x.levelKeyType(x.keys[i-1].Fd) + x.g.P(helper.Indent(ident+1), "var mapKey = new ", levelIndexKeyType, "(", strings.Join(fields, ", "), ");") + x.g.P(helper.Indent(ident+1), "var map = ", x.orderedIndexContainerName(index, i), ".TryGetValue(mapKey, out var existingMap) ?") + x.g.P(helper.Indent(ident+1), "existingMap : ", x.orderedIndexContainerName(index, i), "[mapKey] = [];") + x.g.P(helper.Indent(ident+1), "var list = map.TryGetValue(", key, ", out var existingList) ?") + x.g.P(helper.Indent(ident+1), "existingList : map[", key, "] = [];") + x.g.P(helper.Indent(ident+1), "list.Add(", parentDataName, ");") + } + x.g.P(helper.Indent(ident), "}") + } +} + +func (x *Generator) genOrderedIndexSorter() { + for lm := x.descriptor.LevelMessage; lm != nil; lm = lm.NextLevel { + for _, index := range lm.OrderedIndexes { + if len(index.SortedColFields) != 0 { + valueType := x.mapValueType(index) + x.g.P(helper.Indent(3), "// OrderedIndex(sort): ", index.Index) + indexContainerName := x.orderedIndexContainerName(index, 0) + sorter := strings.TrimPrefix(indexContainerName, "_") + "Comparison" + x.g.P(helper.Indent(3), "Comparison<", valueType, "> ", sorter, " = (a, b) =>") + var keys helper.MapKeySlice + for _, field := range index.SortedColFields { + fieldName, _ := x.parseKeyFieldNameAndSuffix(field) + keys = keys.AddMapKey(helper.MapKey{Name: fieldName}) + } + x.g.P(helper.Indent(4), "(", keys.GenCustom(func(key helper.MapKey) string { return "a" + key.Name }, ", "), ").CompareTo((", keys.GenCustom(func(key helper.MapKey) string { return "b" + key.Name }, ", "), "));") + x.g.P(helper.Indent(3), "foreach (var itemList in ", indexContainerName, ".Values)") + x.g.P(helper.Indent(3), "{") + x.g.P(helper.Indent(4), "itemList.Sort(", sorter, ");") + x.g.P(helper.Indent(3), "}") + // Iterate all leveled containers. + for i := 1; i < lm.MapDepth; i++ { + x.g.P(helper.Indent(3), "foreach (var itemDict in ", x.orderedIndexContainerName(index, i), ".Values)") + x.g.P(helper.Indent(3), "{") + x.g.P(helper.Indent(4), "foreach (var itemList in itemDict.Values)") + x.g.P(helper.Indent(4), "{") + x.g.P(helper.Indent(5), "itemList.Sort(", sorter, ");") + x.g.P(helper.Indent(4), "}") + x.g.P(helper.Indent(3), "}") + } + } + } + } +} + +func (x *Generator) genOrderedIndexFinders() { + if !x.needGenerateOrderedIndex() { + return + } + for lm := x.descriptor.LevelMessage; lm != nil; lm = lm.NextLevel { + for _, index := range lm.OrderedIndexes { + mapType := x.orderedIndexMapType(index) + mapValueType := x.mapValueType(index) + indexContainerName := x.orderedIndexContainerName(index, 0) + + x.g.P() + x.g.P(helper.Indent(2), "// OrderedIndex: ", index.Index) + x.g.P() + x.g.P(helper.Indent(2), "/// ") + x.g.P(helper.Indent(2), "/// Find", index.Name(), "Map finds the ordered index: key(", index.Index, ") to value(", mapValueType, ") sorted map.") + x.g.P(helper.Indent(2), "/// One key may correspond to multiple values, which are represented by a list.") + x.g.P(helper.Indent(2), "/// ") + x.g.P(helper.Indent(2), "public ref readonly ", mapType, " Find", index.Name(), "Map() => ref ", indexContainerName, ";") + x.g.P() + + keyType := x.orderedIndexMapKeyType(index) + keys := x.orderedIndexKeys(index) + params := keys.GenGetParams() + args := keys.GenGetArguments() + x.g.P(helper.Indent(2), "/// ") + x.g.P(helper.Indent(2), "/// Find", index.Name(), " finds a list of all values of the given key(s).") + x.g.P(helper.Indent(2), "/// ") + x.g.P(helper.Indent(2), "public List<", mapValueType, ">? Find", index.Name(), "(", params, ") =>") + if len(index.ColFields) == 1 { + x.g.P(helper.Indent(3), indexContainerName, ".TryGetValue(", args, ", out var value) ? value : null;") + } else { + x.g.P(helper.Indent(3), indexContainerName, ".TryGetValue(new ", keyType, "(", args, "), out var value) ? value : null;") + } + x.g.P() + + x.g.P(helper.Indent(2), "/// ") + x.g.P(helper.Indent(2), "/// FindFirst", index.Name(), " finds the first value of the given key(s),") + x.g.P(helper.Indent(2), "/// or null if no value found.") + x.g.P(helper.Indent(2), "/// ") + x.g.P(helper.Indent(2), "public ", mapValueType, "? FindFirst", index.Name(), "(", params, ") =>") + x.g.P(helper.Indent(3), "Find", index.Name(), "(", args, ")?.FirstOrDefault();") + + for i := 1; i < lm.MapDepth; i++ { + indexContainerName := x.orderedIndexContainerName(index, i) + partKeys := x.keys[:i] + partParams := partKeys.GenGetParams() + partArgs := partKeys.GenGetArguments() + x.g.P() + x.g.P(helper.Indent(2), "/// ") + x.g.P(helper.Indent(2), "/// Find", index.Name(), "Map", i, " finds the ordered index: key(", index.Index, ") to value(", mapValueType, "),") + x.g.P(helper.Indent(2), "/// which is the upper ", loadutil.Ordinal(i), "-level sorted map specified by (", partArgs, ").") + x.g.P(helper.Indent(2), "/// One key may correspond to multiple values, which are represented by a list.") + x.g.P(helper.Indent(2), "/// ") + x.g.P(helper.Indent(2), "public ", mapType, "? Find", index.Name(), "Map", i, "(", partParams, ") =>") + if len(partKeys) == 1 { + x.g.P(helper.Indent(3), indexContainerName, ".TryGetValue(", partArgs, ", out var value) ? value : null;") + } else { + levelIndexKeyType := x.levelKeyType(x.keys[i-1].Fd) + x.g.P(helper.Indent(3), indexContainerName, ".TryGetValue(new ", levelIndexKeyType, "(", partArgs, "), out var value) ? value : null;") + } + + x.g.P() + x.g.P(helper.Indent(2), "/// ") + x.g.P(helper.Indent(2), "/// Find", index.Name(), i, " finds a list of all values of the given key(s) in the upper ", loadutil.Ordinal(i), "-level sorted map") + x.g.P(helper.Indent(2), "/// specified by (", partArgs, ").") + x.g.P(helper.Indent(2), "/// ") + x.g.P(helper.Indent(2), "public List<", mapValueType, ">? Find", index.Name(), i, "(", partParams, ", ", params, ") =>") + if len(index.ColFields) == 1 { + x.g.P(helper.Indent(3), "Find", index.Name(), "Map", i, "(", partArgs, ")?.TryGetValue(", args, ", out var value) == true ? value : null;") + } else { + x.g.P(helper.Indent(3), "Find", index.Name(), "Map", i, "(", partArgs, ")?.TryGetValue(new ", keyType, "(", args, "), out var value) == true ? value : null;") + } + + x.g.P() + x.g.P(helper.Indent(2), "/// ") + x.g.P(helper.Indent(2), "/// FindFirst", index.Name(), i, " finds the first value of the given key(s) in the upper ", loadutil.Ordinal(i), "-level sorted map") + x.g.P(helper.Indent(2), "/// specified by (", partArgs, "), or null if no value found.") + x.g.P(helper.Indent(2), "/// ") + x.g.P(helper.Indent(2), "public ", mapValueType, "? FindFirst", index.Name(), i, "(", partParams, ", ", params, ") =>") + x.g.P(helper.Indent(3), "Find", index.Name(), i, "(", partArgs, ", ", args, ")?.FirstOrDefault();") + } + } + } +} diff --git a/cmd/protoc-gen-csharp-tableau-loader/main.go b/cmd/protoc-gen-csharp-tableau-loader/main.go new file mode 100644 index 00000000..bad35e59 --- /dev/null +++ b/cmd/protoc-gen-csharp-tableau-loader/main.go @@ -0,0 +1,38 @@ +package main + +import ( + "flag" + "fmt" + + "github.com/tableauio/loader/internal/options" + "google.golang.org/protobuf/compiler/protogen" + "google.golang.org/protobuf/types/pluginpb" +) + +const version = "0.1.0" + +func main() { + showVersion := flag.Bool("version", false, "print the version and exit") + flag.Parse() + if *showVersion { + fmt.Printf("protoc-gen-csharp-tableau-loader %v\n", version) + return + } + + var flags flag.FlagSet + + protogen.Options{ + ParamFunc: flags.Set, + }.Run(func(gen *protogen.Plugin) error { + gen.SupportedFeatures = uint64(pluginpb.CodeGeneratorResponse_FEATURE_PROTO3_OPTIONAL) + for _, f := range gen.Files { + if !options.NeedGenFile(f) { + continue + } + generateMessager(gen, f) + } + generateHub(gen) + generateEmbed(gen) + return nil + }) +} diff --git a/cmd/protoc-gen-csharp-tableau-loader/messager.go b/cmd/protoc-gen-csharp-tableau-loader/messager.go new file mode 100644 index 00000000..d51949ae --- /dev/null +++ b/cmd/protoc-gen-csharp-tableau-loader/messager.go @@ -0,0 +1,173 @@ +package main + +import ( + "fmt" + "path/filepath" + + "github.com/iancoleman/strcase" + "github.com/tableauio/loader/cmd/protoc-gen-csharp-tableau-loader/helper" + "github.com/tableauio/loader/cmd/protoc-gen-csharp-tableau-loader/indexes" + "github.com/tableauio/loader/cmd/protoc-gen-csharp-tableau-loader/orderedmap" + "github.com/tableauio/loader/internal/extensions" + "github.com/tableauio/loader/internal/index" + "github.com/tableauio/loader/internal/loadutil" + "github.com/tableauio/tableau/proto/tableaupb" + "google.golang.org/protobuf/compiler/protogen" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/reflect/protoreflect" + "google.golang.org/protobuf/types/descriptorpb" +) + +// golbal container for record all proto filenames and messager names +var messagers []string + +// generateMessager generates a protoconf file corresponding to the protobuf file. +// Each wrapped struct type implement the Messager interface. +func generateMessager(gen *protogen.Plugin, file *protogen.File) { + + filename := filepath.Join(strcase.ToCamel(file.GeneratedFilenamePrefix) + "." + extensions.PC + ".cs") + g := gen.NewGeneratedFile(filename, "") + helper.GenerateFileHeader(gen, file, g, version) + generateFileContent(gen, file, g) +} + +// generateFileContent generates struct type definitions. +func generateFileContent(gen *protogen.Plugin, file *protogen.File, g *protogen.GeneratedFile) { + g.P(staticMessagerContent1) + var fileMessagers []string + firstMessager := true + for _, message := range file.Messages { + opts := message.Desc.Options().(*descriptorpb.MessageOptions) + worksheet := proto.GetExtension(opts, tableaupb.E_Worksheet).(*tableaupb.WorksheetOptions) + if worksheet != nil { + if !firstMessager { + g.P() + } + firstMessager = false + genMessage(gen, g, message) + + messagerName := string(message.Desc.Name()) + fileMessagers = append(fileMessagers, messagerName) + } + } + messagers = append(messagers, fileMessagers...) + g.P(staticMessagerContent2) +} + +// genMessage generates a message definition. +func genMessage(gen *protogen.Plugin, g *protogen.GeneratedFile, message *protogen.Message) { + messagerName := string(message.Desc.Name()) + indexDescriptor := index.ParseIndexDescriptor(message.Desc) + + orderedMapGenerator := orderedmap.NewGenerator(g, message) + indexGenerator := indexes.NewGenerator(g, indexDescriptor, message) + + g.P(helper.Indent(1), "/// ") + g.P(helper.Indent(1), "/// ", messagerName, " is a wrapper around protobuf message ", helper.ParseCsharpClassType(message.Desc), ".") + g.P(helper.Indent(1), "/// ") + g.P(helper.Indent(1), "public class ", messagerName, " : Messager, IMessagerName") + g.P(helper.Indent(1), "{") + // type definitions + orderedMapGenerator.GenOrderedMapTypeDef() + indexGenerator.GenIndexTypeDef() + g.P(helper.Indent(2), "private ", helper.ParseCsharpClassType(message.Desc), " _data = new();") + g.P() + g.P(helper.Indent(2), "/// ") + g.P(helper.Indent(2), "/// Name returns the ", messagerName, "'s message name.") + g.P(helper.Indent(2), "/// ") + g.P(helper.Indent(2), "public static string Name() => ", helper.ParseCsharpClassType(message.Desc), ".Descriptor.Name;") + g.P() + g.P(helper.Indent(2), "/// ") + g.P(helper.Indent(2), "/// Load loads ", messagerName, "'s content in the given dir, based on format and messager options.") + g.P(helper.Indent(2), "/// ") + g.P(helper.Indent(2), "public override bool Load(string dir, Format fmt, in Load.MessagerOptions? options = null)") + g.P(helper.Indent(2), "{") + g.P(helper.Indent(3), "var start = DateTime.Now;") + g.P(helper.Indent(3), "try") + g.P(helper.Indent(3), "{") + g.P(helper.Indent(4), "_data = (", helper.ParseCsharpClassType(message.Desc), ")(") + g.P(helper.Indent(5), "Tableau.Load.LoadMessagerInDir(", helper.ParseCsharpClassType(message.Desc), ".Descriptor, dir, fmt, options)") + g.P(helper.Indent(5), "?? throw new InvalidOperationException()") + g.P(helper.Indent(4), ");") + g.P(helper.Indent(3), "}") + g.P(helper.Indent(3), "catch (Exception ex)") + g.P(helper.Indent(3), "{") + g.P(helper.Indent(4), "if (string.IsNullOrEmpty(Util.GetErrMsg()))") + g.P(helper.Indent(4), "{") + g.P(helper.Indent(5), "Util.SetErrMsg($\"failed to load ", messagerName, ": {ex.Message}\");") + g.P(helper.Indent(4), "}") + g.P(helper.Indent(4), "return false;") + g.P(helper.Indent(3), "}") + g.P(helper.Indent(3), "LoadStats.Duration = DateTime.Now - start;") + g.P(helper.Indent(3), "return ProcessAfterLoad();") + g.P(helper.Indent(2), "}") + g.P() + g.P(helper.Indent(2), "/// ") + g.P(helper.Indent(2), "/// Data returns the ", messagerName, "'s inner message data.") + g.P(helper.Indent(2), "/// ") + g.P(helper.Indent(2), "public ref readonly ", helper.ParseCsharpClassType(message.Desc), " Data() => ref _data;") + g.P() + g.P(helper.Indent(2), "/// ") + g.P(helper.Indent(2), "/// Message returns the ", messagerName, "'s inner message data.") + g.P(helper.Indent(2), "/// ") + g.P(helper.Indent(2), "public override pb::IMessage? Message() => _data;") + + if orderedMapGenerator.NeedGenerate() || indexGenerator.NeedGenerate() { + g.P() + g.P(helper.Indent(2), "/// ") + g.P(helper.Indent(2), "/// ProcessAfterLoad runs after this messager is loaded.") + g.P(helper.Indent(2), "/// ") + g.P(helper.Indent(2), "protected override bool ProcessAfterLoad()") + g.P(helper.Indent(2), "{") + orderedMapGenerator.GenOrderedMapLoader() + indexGenerator.GenIndexLoader() + g.P(helper.Indent(3), "return true;") + g.P(helper.Indent(2), "}") + } + + // syntactic sugar for accessing map items + genMapGetters(gen, g, message.Desc, 1, nil, messagerName) + orderedMapGenerator.GenOrderedMapGetters() + indexGenerator.GenIndexFinders() + g.P(helper.Indent(1), "}") +} + +func genMapGetters(gen *protogen.Plugin, g *protogen.GeneratedFile, md protoreflect.MessageDescriptor, depth int, keys helper.MapKeySlice, messagerName string) { + for i := 0; i < md.Fields().Len(); i++ { + fd := md.Fields().Get(i) + if fd.IsMap() { + keys = keys.AddMapKey(helper.MapKey{ + Type: helper.ParseMapKeyType(fd.MapKey()), + Name: helper.ParseMapFieldNameAsFuncParam(fd), + }) + getter := fmt.Sprintf("Get%v", depth) + g.P() + g.P(helper.Indent(2), "/// ") + g.P(helper.Indent(2), "/// ", getter, " finds value in the ", loadutil.Ordinal(depth), "-level map.") + g.P(helper.Indent(2), "/// It will return null if the key is not found.") + g.P(helper.Indent(2), "/// ") + + lastKeyName := keys[len(keys)-1].Name + if depth == 1 { + g.P(helper.Indent(2), "public ", helper.ParseMapValueType(fd), "? ", getter, "(", keys.GenGetParams(), ") =>") + g.P(helper.Indent(3), "_data.", helper.ParseCsharpPropertyName(fd), "?.TryGetValue(", lastKeyName, ", out var val) == true ? val : null;") + } else { + prevKeys := keys[:len(keys)-1] + prevGetter := fmt.Sprintf("Get%v", depth-1) + g.P(helper.Indent(2), "public ", helper.ParseMapValueType(fd), "? ", getter, "(", keys.GenGetParams(), ") =>") + g.P(helper.Indent(3), prevGetter, "(", prevKeys.GenGetArguments(), ")?.", helper.ParseCsharpPropertyName(fd), "?.TryGetValue(", lastKeyName, ", out var val) == true ? val : null;") + } + + if fd.MapValue().Kind() == protoreflect.MessageKind { + genMapGetters(gen, g, fd.MapValue().Message(), depth+1, keys, messagerName) + } + break + } + } +} + +const staticMessagerContent1 = `using pb = global::Google.Protobuf; +namespace Tableau +{` + +const staticMessagerContent2 = `}` diff --git a/cmd/protoc-gen-csharp-tableau-loader/orderedmap/ordered_map.go b/cmd/protoc-gen-csharp-tableau-loader/orderedmap/ordered_map.go new file mode 100644 index 00000000..6bcb2d2b --- /dev/null +++ b/cmd/protoc-gen-csharp-tableau-loader/orderedmap/ordered_map.go @@ -0,0 +1,205 @@ +package orderedmap + +import ( + "fmt" + + "github.com/tableauio/loader/cmd/protoc-gen-csharp-tableau-loader/helper" + "github.com/tableauio/loader/internal/loadutil" + "github.com/tableauio/loader/internal/options" + "google.golang.org/protobuf/compiler/protogen" + "google.golang.org/protobuf/reflect/protoreflect" +) + +type Generator struct { + g *protogen.GeneratedFile + message *protogen.Message +} + +func NewGenerator(g *protogen.GeneratedFile, message *protogen.Message) *Generator { + return &Generator{ + g: g, + message: message, + } +} + +func (x *Generator) NeedGenerate() bool { + return options.NeedGenOrderedMap(x.message.Desc, options.LangCS) +} + +func (x *Generator) mapType(mapFd protoreflect.FieldDescriptor) string { + return fmt.Sprintf("OrderedMap_%sMap", helper.ParseLeveledMapPrefix(x.message.Desc, mapFd)) +} + +func (x *Generator) mapValueType(mapFd protoreflect.FieldDescriptor) string { + return fmt.Sprintf("OrderedMap_%sValue", helper.ParseLeveledMapPrefix(x.message.Desc, mapFd)) +} + +func (x *Generator) mapValueFieldType(fd protoreflect.FieldDescriptor) string { + nextMapFD := getNextLevelMapFD(fd.MapValue()) + if nextMapFD != nil { + return x.mapValueType(fd) + } + return helper.ParseMapValueType(fd) +} + +func (x *Generator) GenOrderedMapTypeDef() { + if !x.NeedGenerate() { + return + } + x.genOrderedMapTypeDef(x.message.Desc, 1, nil) +} + +func (x *Generator) genOrderedMapTypeDef(md protoreflect.MessageDescriptor, depth int, keys helper.MapKeySlice) { + for i := 0; i < md.Fields().Len(); i++ { + fd := md.Fields().Get(i) + if fd.IsMap() { + if depth == 1 { + x.g.P(helper.Indent(2), "// OrderedMap types.") + } + nextKeys := keys.AddMapKey(helper.MapKey{ + Type: helper.ParseMapKeyType(fd.MapKey()), + Name: helper.ParseMapFieldNameAsKeyStructFieldName(fd), + }) + keyType := nextKeys[len(nextKeys)-1].Type + if fd.MapValue().Kind() == protoreflect.MessageKind { + x.genOrderedMapTypeDef(fd.MapValue().Message(), depth+1, nextKeys) + } + orderedMap := x.mapType(fd) + orderedMapValue := x.mapValueType(fd) + nextMapFD := getNextLevelMapFD(fd.MapValue()) + if nextMapFD != nil { + currValueType := helper.ParseCsharpType(fd.MapValue()) + nextOrderedMap := x.mapType(nextMapFD) + x.g.P(helper.Indent(2), "public class ", orderedMapValue, "(", nextOrderedMap, " item1, ", currValueType, " item2)") + x.g.P(helper.Indent(3), ": Tuple<", nextOrderedMap, ", ", currValueType, ">(item1, item2);") + } + x.g.P(helper.Indent(2), "public class ", orderedMap, " : SortedDictionary<", keyType, ", ", x.mapValueFieldType(fd), ">;") + x.g.P() + if depth == 1 { + x.g.P(helper.Indent(2), "private ", orderedMap, " _orderedMap = [];") + x.g.P() + } + break + } + } +} + +func (x *Generator) GenOrderedMapLoader() { + if !x.NeedGenerate() { + return + } + x.genOrderedMapLoader(x.message.Desc, 1) +} + +func (x *Generator) genOrderedMapLoader(md protoreflect.MessageDescriptor, depth int) { + for i := 0; i < md.Fields().Len(); i++ { + fd := md.Fields().Get(i) + if fd.IsMap() { + if depth == 1 { + x.g.P(helper.Indent(3), "// OrderedMap init.") + x.g.P(helper.Indent(3), "_orderedMap.Clear();") + } + orderedMapValue := x.mapValueType(fd) + keyName := fmt.Sprintf("key%d", depth) + valueName := fmt.Sprintf("value%d", depth) + + tmpOrderedMapName := fmt.Sprintf("ordered_map%d", depth) + + prevContainer := fmt.Sprintf("value%d", depth-1) + prevTmpOrderedMapName := fmt.Sprintf("ordered_map%d", depth-1) + if depth == 1 { + prevContainer = "_data" + prevTmpOrderedMapName = "_orderedMap" + } + x.g.P(helper.Indent(depth+2), "foreach (var (", keyName, ", ", valueName, ") in ", prevContainer, ".", helper.ParseCsharpPropertyName(fd), ")") + x.g.P(helper.Indent(depth+2), "{") + nextMapFD := getNextLevelMapFD(fd.MapValue()) + if nextMapFD != nil { + nextOrderedMap := x.mapType(nextMapFD) + x.g.P(helper.Indent(depth+3), "var ", tmpOrderedMapName, " = new ", nextOrderedMap, "();") + } + if fd.MapValue().Kind() == protoreflect.MessageKind { + x.genOrderedMapLoader(fd.MapValue().Message(), depth+1) + } + + if nextMapFD != nil { + x.g.P(helper.Indent(depth+3), prevTmpOrderedMapName, "[", keyName, "] = new ", orderedMapValue, "(", tmpOrderedMapName, ", ", valueName, ");") + } else { + x.g.P(helper.Indent(depth+3), prevTmpOrderedMapName, "[", keyName, "] = ", valueName, ";") + } + x.g.P(helper.Indent(depth+2), "}") + break + } + } +} + +func (x *Generator) GenOrderedMapGetters() { + if !x.NeedGenerate() { + return + } + x.genOrderedMapGetters(x.message.Desc, 1, nil) +} + +func (x *Generator) genOrderedMapGetters(md protoreflect.MessageDescriptor, depth int, keys helper.MapKeySlice) { + genGetterName := func(depth int) string { + getter := "GetOrderedMap" + if depth > 1 { + getter = fmt.Sprintf("GetOrderedMap%v", depth-1) + } + return getter + } + for i := 0; i < md.Fields().Len(); i++ { + fd := md.Fields().Get(i) + if fd.IsMap() { + x.g.P() + if depth == 1 { + x.g.P(helper.Indent(2), "// OrderedMap accessers.") + } + getter := genGetterName(depth) + orderedMap := x.mapType(fd) + if depth == 1 { + x.g.P(helper.Indent(2), "/// ") + x.g.P(helper.Indent(2), "/// ", getter, " returns the ", loadutil.Ordinal(depth), "-level ordered map.") + x.g.P(helper.Indent(2), "/// ") + x.g.P(helper.Indent(2), "public ref readonly ", orderedMap, " ", getter, "() => ref _orderedMap;") + } else { + x.g.P(helper.Indent(2), "/// ") + x.g.P(helper.Indent(2), "/// ", getter, " finds value in the ", loadutil.Ordinal(depth-1), "-level ordered map.") + x.g.P(helper.Indent(2), "/// It will return null if the key is not found.") + x.g.P(helper.Indent(2), "/// ") + lastKeyName := keys[len(keys)-1].Name + if depth == 2 { + x.g.P(helper.Indent(2), "public ", orderedMap, "? ", getter, "(", keys.GenGetParams(), ") =>") + x.g.P(helper.Indent(3), "_orderedMap.TryGetValue(", lastKeyName, ", out var value) ? value.Item1 : null;") + } else { + prevKeys := keys[:len(keys)-1] + prevGetter := genGetterName(depth - 1) + x.g.P(helper.Indent(2), "public ", orderedMap, "? ", getter, "(", keys.GenGetParams(), ") =>") + x.g.P(helper.Indent(3), prevGetter, "(", prevKeys.GenGetArguments(), ")?.TryGetValue(", lastKeyName, ", out var value) == true ? value.Item1 : null;") + } + } + + nextKeys := keys.AddMapKey(helper.MapKey{ + Type: helper.ParseMapKeyType(fd.MapKey()), + Name: helper.ParseMapFieldNameAsFuncParam(fd), + }) + if fd.MapValue().Kind() == protoreflect.MessageKind { + x.genOrderedMapGetters(fd.MapValue().Message(), depth+1, nextKeys) + } + break + } + } +} + +func getNextLevelMapFD(fd protoreflect.FieldDescriptor) protoreflect.FieldDescriptor { + if fd.Kind() == protoreflect.MessageKind { + md := fd.Message() + for i := 0; i < md.Fields().Len(); i++ { + fd := md.Fields().Get(i) + if fd.IsMap() { + return fd + } + } + } + return nil +} diff --git a/internal/options/options.go b/internal/options/options.go index c56d5b12..df5f801a 100644 --- a/internal/options/options.go +++ b/internal/options/options.go @@ -22,6 +22,7 @@ type Language = string const ( LangCPP Language = "cpp" LangGO Language = "go" + LangCS Language = "cs" ) func NeedGenOrderedMap(md protoreflect.MessageDescriptor, lang Language) bool { diff --git a/test/csharp-tableau-loader/Loader.csproj b/test/csharp-tableau-loader/Loader.csproj new file mode 100644 index 00000000..500449ad --- /dev/null +++ b/test/csharp-tableau-loader/Loader.csproj @@ -0,0 +1,15 @@ + + + + Exe + net8.0 + enable + enable + CS8981 + + + + + + + diff --git a/test/csharp-tableau-loader/Program.cs b/test/csharp-tableau-loader/Program.cs new file mode 100644 index 00000000..e539ea70 --- /dev/null +++ b/test/csharp-tableau-loader/Program.cs @@ -0,0 +1,107 @@ +class Program +{ + static void Main(string[] _) + { + Tableau.Registry.Init(); + + var options = new Tableau.HubOptions + { + Filter = name => name != "TaskConf" + }; + var hub = new Tableau.Hub(options); + var loadOptions = new Tableau.Load.Options + { + IgnoreUnknownFields = true + }; + if (!hub.Load("../testdata/conf", Tableau.Format.JSON, loadOptions)) + { + Console.WriteLine("Failed to load configurations"); + return; + } + + var taskConf = hub.Get(); + if (taskConf is null) + { + Console.WriteLine("TaskConf is null"); + } + else + { + Console.WriteLine($"TaskConf: {taskConf.Data()}"); + Console.WriteLine($"TaskConf Load duration: {taskConf.GetStats().Duration.TotalMilliseconds} ms"); + } + + var heroConf = hub.Get(); + if (heroConf is null) + { + Console.WriteLine("HeroConf is null"); + } + else + { + Console.WriteLine($"HeroConf: {heroConf.Data()}"); + Console.WriteLine($"HeroConf Load duration: {heroConf.GetStats().Duration.TotalMilliseconds} ms"); + // Traverse top-level OrderedMap (HeroOrderedMap) + var heroOrderedMap = heroConf.GetOrderedMap(); + if (heroOrderedMap != null) + { + Console.WriteLine("Hero OrderedMap:"); + foreach (var heroPair in heroOrderedMap) + { + Console.WriteLine($"Hero: {heroPair.Key}"); + Console.WriteLine($" - Hero Data: {heroPair.Value.Item2}"); + // Traverse nested Attr OrderedMap + var attrOrderedMap = heroPair.Value.Item1; + if (attrOrderedMap != null && attrOrderedMap.Count > 0) + { + Console.WriteLine(" Attributes:"); + foreach (var attrPair in attrOrderedMap) + { + Console.WriteLine($" - {attrPair.Key}: {attrPair.Value}"); + } + } + } + } + } + + var itemConf = hub.Get(); + if (itemConf is null) + { + Console.WriteLine("ItemConf is null"); + } + else + { + Console.WriteLine($"ItemConf: {itemConf.Data()}"); + Console.WriteLine($"ItemConf Load duration: {itemConf.GetStats().Duration.TotalMilliseconds} ms"); + var itemConf2 = hub.GetItemConf(); + Console.WriteLine($"hub.Get() returns same instance with hub.GetItemConf(): {ReferenceEquals(itemConf, itemConf2)}"); + var itemInfoMap = itemConf.FindItemInfoMap(); + if (itemInfoMap != null) + { + Console.WriteLine("ItemInfoMap Contents:"); + foreach (var itemPair in itemInfoMap) + { + Console.WriteLine($" - {itemPair.Key}: "); + foreach (var element in itemPair.Value) + { + Console.WriteLine($" - {element}"); + } + } + } + } + + LoadBin(); + } + + static void LoadBin() + { + Console.WriteLine("LoadBin"); + var heroConf = new Tableau.HeroConf(); + if (heroConf.Load("../testdata/bin", Tableau.Format.Bin)) + { + Console.WriteLine($"HeroConf: {heroConf.Data()}"); + } + if (!heroConf.Load("../testdata/notexist", Tableau.Format.Bin)) + { + Console.WriteLine("HeroConf not exist"); + } + } +} \ No newline at end of file diff --git a/test/csharp-tableau-loader/csharp-tableau-loader.sln b/test/csharp-tableau-loader/csharp-tableau-loader.sln new file mode 100644 index 00000000..d31764f3 --- /dev/null +++ b/test/csharp-tableau-loader/csharp-tableau-loader.sln @@ -0,0 +1,24 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.2.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Loader", "Loader.csproj", "{E8053C8C-98ED-7494-D874-8E12A72FA028}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {E8053C8C-98ED-7494-D874-8E12A72FA028}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E8053C8C-98ED-7494-D874-8E12A72FA028}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E8053C8C-98ED-7494-D874-8E12A72FA028}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E8053C8C-98ED-7494-D874-8E12A72FA028}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {80988C2E-15FF-4FCD-B5F0-A75A1AD74321} + EndGlobalSection +EndGlobal diff --git a/test/csharp-tableau-loader/gen.bat b/test/csharp-tableau-loader/gen.bat new file mode 100644 index 00000000..3a3d50f7 --- /dev/null +++ b/test/csharp-tableau-loader/gen.bat @@ -0,0 +1,83 @@ +@echo off +setlocal +setlocal enabledelayedexpansion + +for /f "delims=" %%i in ('git rev-parse --show-toplevel') do set repoRoot=%%i +cd /d "%repoRoot%" + +REM Allow overriding protoc via environment variable. +REM Default to locally compiled protoc for local development; fallback to system protoc. +if not defined PROTOC ( + if exist "%repoRoot%\third_party\_submodules\protobuf\cmake\build\protoc.exe" ( + set "PROTOC=%repoRoot%\third_party\_submodules\protobuf\cmake\build\protoc.exe" + ) else ( + where protoc >nul 2>nul + if !errorlevel! equ 0 ( + for /f "delims=" %%p in ('where protoc') do set "PROTOC=%%p" + ) else ( + echo Error: protoc not found. Please build protobuf submodule or install protoc. >&2 + exit /b 1 + ) + ) +) +REM Allow overriding protobuf include path via environment variable. +REM Default to local submodule source; fallback to system include path. +if not defined PROTOBUF_PROTO ( + if exist "%repoRoot%\third_party\_submodules\protobuf\src\google\protobuf" ( + set "PROTOBUF_PROTO=%repoRoot%\third_party\_submodules\protobuf\src" + ) else ( + for /f "delims=" %%p in ('where protoc 2^>nul') do set "_PROTOC_DIR=%%~dpp" + if defined _PROTOC_DIR ( + set "PROTOBUF_PROTO=!_PROTOC_DIR!..\include" + ) else ( + set "PROTOBUF_PROTO=%repoRoot%\third_party\_submodules\protobuf\src" + ) + ) +) +set "TABLEAU_PROTO=%repoRoot%\third_party\_submodules\tableau\proto" +set "ROOTDIR=%repoRoot%\test\csharp-tableau-loader" +set "PLGUIN_DIR=%repoRoot%\cmd\protoc-gen-csharp-tableau-loader" +set "PROTOCONF_IN=%repoRoot%\test\proto" +set "PROTOCONF_OUT=%ROOTDIR%\protoconf" +set "LOADER_OUT=%ROOTDIR%\tableau" + +REM remove old generated files +rmdir /s /q "%PROTOCONF_OUT%" "%LOADER_OUT%" 2>nul +mkdir "%PROTOCONF_OUT%" "%LOADER_OUT%" + +REM build protoc plugin of loader +pushd "%PLGUIN_DIR%" +go build +popd + +set "PATH=%PATH%;%PLGUIN_DIR%" + +set protoFiles= +pushd "%PROTOCONF_IN%" +for /R %%f in (*.proto) do ( + set protoFiles=!protoFiles! "%%f" +) +popd +"%PROTOC%" ^ +--csharp_out="%PROTOCONF_OUT%" ^ +--csharp-tableau-loader_out="%LOADER_OUT%" ^ +--csharp-tableau-loader_opt=paths=source_relative ^ +--proto_path="%PROTOBUF_PROTO%" ^ +--proto_path="%TABLEAU_PROTO%" ^ +--proto_path="%PROTOCONF_IN%" ^ +!protoFiles! + +set "TABLEAU_IN=%TABLEAU_PROTO%\tableau\protobuf" +set "TABLEAU_OUT=%ROOTDIR%\protoconf\tableau" +REM remove old generated files +rmdir /s /q "%TABLEAU_OUT%" 2>nul +mkdir "%TABLEAU_OUT%" + +"%PROTOC%" ^ +--csharp_out="%TABLEAU_OUT%" ^ +--proto_path="%PROTOBUF_PROTO%" ^ +--proto_path="%TABLEAU_PROTO%" ^ +"%TABLEAU_IN%\tableau.proto" "%TABLEAU_IN%\wellknown.proto" + +endlocal +endlocal diff --git a/test/csharp-tableau-loader/gen.sh b/test/csharp-tableau-loader/gen.sh new file mode 100644 index 00000000..95826453 --- /dev/null +++ b/test/csharp-tableau-loader/gen.sh @@ -0,0 +1,72 @@ +#!/bin/bash + +# set -eux +set -e +set -o pipefail + +cd "$(git rev-parse --show-toplevel)" + +# Allow overriding protoc via environment variable. +# Default to locally compiled protoc for local development; fallback to system protoc. +LOCAL_PROTOC="./third_party/_submodules/protobuf/cmake/build/protoc" +if [ -z "$PROTOC" ]; then + if [ -x "$LOCAL_PROTOC" ]; then + PROTOC="$LOCAL_PROTOC" + else + PROTOC="$(which protoc 2>/dev/null || true)" + fi +fi +if [ -z "$PROTOC" ]; then + echo "Error: protoc not found. Please build protobuf submodule or install protoc." >&2 + exit 1 +fi +# Allow overriding protobuf include path via environment variable. +# Default to local submodule source; fallback to system include path. +LOCAL_PROTOBUF_PROTO="./third_party/_submodules/protobuf/src" +if [ -z "$PROTOBUF_PROTO" ]; then + if [ -d "$LOCAL_PROTOBUF_PROTO/google/protobuf" ]; then + PROTOBUF_PROTO="$LOCAL_PROTOBUF_PROTO" + else + PROTOBUF_PROTO="$(pkg-config --variable=includedir protobuf 2>/dev/null || echo /usr/include)" + fi +fi +TABLEAU_PROTO="./third_party/_submodules/tableau/proto" +ROOTDIR="./test/csharp-tableau-loader" +PLUGIN_DIR="./cmd/protoc-gen-csharp-tableau-loader" +PROTOCONF_IN="./test/proto" +PROTOCONF_OUT="${ROOTDIR}/protoconf" +LOADER_OUT="${ROOTDIR}/tableau" + +# remove old generated files +rm -rfv "$PROTOCONF_OUT" "$LOADER_OUT" +mkdir -p "$PROTOCONF_OUT" "$LOADER_OUT" + +# build +cd "${PLUGIN_DIR}" && go build && cd - + +export PATH="$(pwd)/${PLUGIN_DIR}:${PATH}" + +# Collect all .proto files (use `find` for cross-platform compatibility). +PROTO_FILES=$(find "$PROTOCONF_IN" -name "*.proto") + +${PROTOC} \ + --csharp_out="$PROTOCONF_OUT" \ + --csharp-tableau-loader_out="$LOADER_OUT" \ + --csharp-tableau-loader_opt=paths=source_relative \ + --proto_path="$PROTOBUF_PROTO" \ + --proto_path="$TABLEAU_PROTO" \ + --proto_path="$PROTOCONF_IN" \ + $PROTO_FILES + +TABLEAU_IN="$TABLEAU_PROTO/tableau/protobuf" +TABLEAU_OUT="${ROOTDIR}/protoconf/tableau" +# remove old generated files +rm -rfv "$TABLEAU_OUT" +mkdir -p "$TABLEAU_OUT" + +${PROTOC} \ + --csharp_out="$TABLEAU_OUT" \ + --proto_path="$PROTOBUF_PROTO" \ + --proto_path="$TABLEAU_PROTO" \ + "$TABLEAU_IN/tableau.proto" \ + "$TABLEAU_IN/wellknown.proto" diff --git a/test/csharp-tableau-loader/tableau/HeroConf.pc.cs b/test/csharp-tableau-loader/tableau/HeroConf.pc.cs new file mode 100644 index 00000000..0a5118e5 --- /dev/null +++ b/test/csharp-tableau-loader/tableau/HeroConf.pc.cs @@ -0,0 +1,176 @@ +// +// Code generated by protoc-gen-csharp-tableau-loader. DO NOT EDIT. +// versions: +// - protoc-gen-csharp-tableau-loader v0.1.0 +// - protoc v3.19.3 +// source: hero_conf.proto +// +#nullable enable +using pb = global::Google.Protobuf; +namespace Tableau +{ + /// + /// HeroConf is a wrapper around protobuf message Protoconf.HeroConf. + /// + public class HeroConf : Messager, IMessagerName + { + // OrderedMap types. + public class OrderedMap_Hero_AttrMap : SortedDictionary; + + public class OrderedMap_HeroValue(OrderedMap_Hero_AttrMap item1, Protoconf.HeroConf.Types.Hero item2) + : Tuple(item1, item2); + public class OrderedMap_HeroMap : SortedDictionary; + + private OrderedMap_HeroMap _orderedMap = []; + + private Protoconf.HeroConf _data = new(); + + /// + /// Name returns the HeroConf's message name. + /// + public static string Name() => Protoconf.HeroConf.Descriptor.Name; + + /// + /// Load loads HeroConf's content in the given dir, based on format and messager options. + /// + public override bool Load(string dir, Format fmt, in Load.MessagerOptions? options = null) + { + var start = DateTime.Now; + try + { + _data = (Protoconf.HeroConf)( + Tableau.Load.LoadMessagerInDir(Protoconf.HeroConf.Descriptor, dir, fmt, options) + ?? throw new InvalidOperationException() + ); + } + catch (Exception ex) + { + if (string.IsNullOrEmpty(Util.GetErrMsg())) + { + Util.SetErrMsg($"failed to load HeroConf: {ex.Message}"); + } + return false; + } + LoadStats.Duration = DateTime.Now - start; + return ProcessAfterLoad(); + } + + /// + /// Data returns the HeroConf's inner message data. + /// + public ref readonly Protoconf.HeroConf Data() => ref _data; + + /// + /// Message returns the HeroConf's inner message data. + /// + public override pb::IMessage? Message() => _data; + + /// + /// ProcessAfterLoad runs after this messager is loaded. + /// + protected override bool ProcessAfterLoad() + { + // OrderedMap init. + _orderedMap.Clear(); + foreach (var (key1, value1) in _data.HeroMap) + { + var ordered_map1 = new OrderedMap_Hero_AttrMap(); + foreach (var (key2, value2) in value1.AttrMap) + { + ordered_map1[key2] = value2; + } + _orderedMap[key1] = new OrderedMap_HeroValue(ordered_map1, value1); + } + return true; + } + + /// + /// Get1 finds value in the 1st-level map. + /// It will return null if the key is not found. + /// + public Protoconf.HeroConf.Types.Hero? Get1(string name) => + _data.HeroMap?.TryGetValue(name, out var val) == true ? val : null; + + /// + /// Get2 finds value in the 2nd-level map. + /// It will return null if the key is not found. + /// + public Protoconf.HeroConf.Types.Hero.Types.Attr? Get2(string name, string title) => + Get1(name)?.AttrMap?.TryGetValue(title, out var val) == true ? val : null; + + // OrderedMap accessers. + /// + /// GetOrderedMap returns the 1st-level ordered map. + /// + public ref readonly OrderedMap_HeroMap GetOrderedMap() => ref _orderedMap; + + /// + /// GetOrderedMap1 finds value in the 1st-level ordered map. + /// It will return null if the key is not found. + /// + public OrderedMap_Hero_AttrMap? GetOrderedMap1(string name) => + _orderedMap.TryGetValue(name, out var value) ? value.Item1 : null; + } + + /// + /// HeroBaseConf is a wrapper around protobuf message Protoconf.HeroBaseConf. + /// + public class HeroBaseConf : Messager, IMessagerName + { + private Protoconf.HeroBaseConf _data = new(); + + /// + /// Name returns the HeroBaseConf's message name. + /// + public static string Name() => Protoconf.HeroBaseConf.Descriptor.Name; + + /// + /// Load loads HeroBaseConf's content in the given dir, based on format and messager options. + /// + public override bool Load(string dir, Format fmt, in Load.MessagerOptions? options = null) + { + var start = DateTime.Now; + try + { + _data = (Protoconf.HeroBaseConf)( + Tableau.Load.LoadMessagerInDir(Protoconf.HeroBaseConf.Descriptor, dir, fmt, options) + ?? throw new InvalidOperationException() + ); + } + catch (Exception ex) + { + if (string.IsNullOrEmpty(Util.GetErrMsg())) + { + Util.SetErrMsg($"failed to load HeroBaseConf: {ex.Message}"); + } + return false; + } + LoadStats.Duration = DateTime.Now - start; + return ProcessAfterLoad(); + } + + /// + /// Data returns the HeroBaseConf's inner message data. + /// + public ref readonly Protoconf.HeroBaseConf Data() => ref _data; + + /// + /// Message returns the HeroBaseConf's inner message data. + /// + public override pb::IMessage? Message() => _data; + + /// + /// Get1 finds value in the 1st-level map. + /// It will return null if the key is not found. + /// + public Base.Hero? Get1(string name) => + _data.HeroMap?.TryGetValue(name, out var val) == true ? val : null; + + /// + /// Get2 finds value in the 2nd-level map. + /// It will return null if the key is not found. + /// + public Base.Item? Get2(string name, string id) => + Get1(name)?.ItemMap?.TryGetValue(id, out var val) == true ? val : null; + } +} diff --git a/test/csharp-tableau-loader/tableau/Hub.pc.cs b/test/csharp-tableau-loader/tableau/Hub.pc.cs new file mode 100644 index 00000000..c95fbebf --- /dev/null +++ b/test/csharp-tableau-loader/tableau/Hub.pc.cs @@ -0,0 +1,209 @@ +// +// Code generated by protoc-gen-csharp-tableau-loader. DO NOT EDIT. +// versions: +// - protoc-gen-csharp-tableau-loader v0.1.0 +// - protoc v3.19.3 +// +#nullable enable +using pb = global::Google.Protobuf; +namespace Tableau +{ + /// + /// MessagerContainer holds all messager instances and provides fast access. + /// + internal class MessagerContainer(in Dictionary? messagerMap = null) + { + public Dictionary MessagerMap = messagerMap ?? []; + public DateTime LastLoadedTime = DateTime.Now; + public ItemConf? ItemConf = InternalGet(messagerMap); + public ActivityConf? ActivityConf = InternalGet(messagerMap); + public ChapterConf? ChapterConf = InternalGet(messagerMap); + public ThemeConf? ThemeConf = InternalGet(messagerMap); + public TaskConf? TaskConf = InternalGet(messagerMap); + public FruitConf? FruitConf = InternalGet(messagerMap); + public Fruit2Conf? Fruit2Conf = InternalGet(messagerMap); + public Fruit3Conf? Fruit3Conf = InternalGet(messagerMap); + public Fruit4Conf? Fruit4Conf = InternalGet(messagerMap); + public Fruit5Conf? Fruit5Conf = InternalGet(messagerMap); + public HeroConf? HeroConf = InternalGet(messagerMap); + public HeroBaseConf? HeroBaseConf = InternalGet(messagerMap); + public PatchReplaceConf? PatchReplaceConf = InternalGet(messagerMap); + public PatchMergeConf? PatchMergeConf = InternalGet(messagerMap); + public RecursivePatchConf? RecursivePatchConf = InternalGet(messagerMap); + + /// + /// Get returns the messager of type T from the container. + /// + public T? Get() where T : Messager, IMessagerName => InternalGet(MessagerMap); + + private static T? InternalGet(in Dictionary? messagerMap) where T : Messager, IMessagerName => + messagerMap?.TryGetValue(T.Name(), out var messager) == true ? (T)messager : null; + } + + /// + /// Atomic provides a thread-safe wrapper for reference types. + /// + internal class Atomic where T : class + { + private T? _value; + + public T? Value + { + get => Interlocked.CompareExchange(ref _value, null, null); + set => Interlocked.Exchange(ref _value, value); + } + } + + /// + /// HubOptions is the options for Hub. + /// + public class HubOptions + { + /// + /// Filter can only filter in certain specific messagers based on the + /// condition that you provide. + /// + public Func? Filter { get; set; } + } + + /// + /// Hub is the messager manager. It manages loading, accessing, and storing + /// all configuration messagers. + /// + public class Hub(HubOptions? options = null) + { + private readonly Atomic _messagerContainer = new(); + private readonly HubOptions? _options = options; + + /// + /// Load fills messages from files in the specified directory and format. + /// + public bool Load(string dir, Format fmt, in Load.Options? options = null) + { + var messagerMap = NewMessagerMap(); + var opts = options ?? new Load.Options(); + foreach (var kvs in messagerMap) + { + string name = kvs.Key; + if (!kvs.Value.Load(dir, fmt, opts.ParseMessagerOptionsByName(name))) + { + Console.Error.WriteLine($"load {name} failed: {Util.GetErrMsg()}"); + return false; + } + } + var tmpHub = new Hub(); + tmpHub.SetMessagerMap(messagerMap); + foreach (var messager in messagerMap) + { + if (!messager.Value.ProcessAfterLoadAll(tmpHub)) + { + Console.Error.WriteLine($"hub call ProcessAfterLoadAll failed, messager: {messager.Key}"); + return false; + } + } + SetMessagerMap(messagerMap); + return true; + } + + /// + /// GetMessagerMap returns the current messager map. + /// + public IReadOnlyDictionary? GetMessagerMap() => _messagerContainer.Value?.MessagerMap; + + /// + /// SetMessagerMap sets the messager map with thread-safe guarantee. + /// + public void SetMessagerMap(in Dictionary map) => _messagerContainer.Value = new MessagerContainer(map); + + /// + /// Get returns the messager of type T from the hub. + /// + public T? Get() where T : Messager, IMessagerName => _messagerContainer.Value?.Get(); + + public ItemConf? GetItemConf() => _messagerContainer.Value?.ItemConf; + + public ActivityConf? GetActivityConf() => _messagerContainer.Value?.ActivityConf; + + public ChapterConf? GetChapterConf() => _messagerContainer.Value?.ChapterConf; + + public ThemeConf? GetThemeConf() => _messagerContainer.Value?.ThemeConf; + + public TaskConf? GetTaskConf() => _messagerContainer.Value?.TaskConf; + + public FruitConf? GetFruitConf() => _messagerContainer.Value?.FruitConf; + + public Fruit2Conf? GetFruit2Conf() => _messagerContainer.Value?.Fruit2Conf; + + public Fruit3Conf? GetFruit3Conf() => _messagerContainer.Value?.Fruit3Conf; + + public Fruit4Conf? GetFruit4Conf() => _messagerContainer.Value?.Fruit4Conf; + + public Fruit5Conf? GetFruit5Conf() => _messagerContainer.Value?.Fruit5Conf; + + public HeroConf? GetHeroConf() => _messagerContainer.Value?.HeroConf; + + public HeroBaseConf? GetHeroBaseConf() => _messagerContainer.Value?.HeroBaseConf; + + public PatchReplaceConf? GetPatchReplaceConf() => _messagerContainer.Value?.PatchReplaceConf; + + public PatchMergeConf? GetPatchMergeConf() => _messagerContainer.Value?.PatchMergeConf; + + public RecursivePatchConf? GetRecursivePatchConf() => _messagerContainer.Value?.RecursivePatchConf; + + /// + /// GetLastLoadedTime returns the time when hub's messager container was last set. + /// + public DateTime? GetLastLoadedTime() => _messagerContainer.Value?.LastLoadedTime; + + /// + /// NewMessagerMap creates a new MessagerMap based on the registered messagers. + /// + private Dictionary NewMessagerMap() + { + var messagerMap = new Dictionary(); + foreach (var kv in Registry.Registrar) + { + if (_options?.Filter?.Invoke(kv.Key) ?? true) + { + messagerMap[kv.Key] = kv.Value(); + } + } + return messagerMap; + } + } + + /// + /// Registry manages the registration of all messager generators. + /// + public class Registry + { + internal static readonly Dictionary> Registrar = []; + + /// + /// Register registers a messager generator for type T. + /// + public static void Register() where T : Messager, IMessagerName, new() => Registrar[T.Name()] = () => new T(); + + /// + /// Init registers all generated messagers. + /// + public static void Init() + { + Register(); + Register(); + Register(); + Register(); + Register(); + Register(); + Register(); + Register(); + Register(); + Register(); + Register(); + Register(); + Register(); + Register(); + Register(); + } + } +} diff --git a/test/csharp-tableau-loader/tableau/IndexConf.pc.cs b/test/csharp-tableau-loader/tableau/IndexConf.pc.cs new file mode 100644 index 00000000..149e4ce7 --- /dev/null +++ b/test/csharp-tableau-loader/tableau/IndexConf.pc.cs @@ -0,0 +1,1215 @@ +// +// Code generated by protoc-gen-csharp-tableau-loader. DO NOT EDIT. +// versions: +// - protoc-gen-csharp-tableau-loader v0.1.0 +// - protoc v3.19.3 +// source: index_conf.proto +// +#nullable enable +using pb = global::Google.Protobuf; +namespace Tableau +{ + /// + /// FruitConf is a wrapper around protobuf message Protoconf.FruitConf. + /// + public class FruitConf : Messager, IMessagerName + { + // OrderedIndex types. + // OrderedIndex: Price + public class OrderedIndex_ItemMap : SortedDictionary>; + + private OrderedIndex_ItemMap _orderedIndexItemMap = []; + + private Dictionary _orderedIndexItemMap1 = []; + + private Protoconf.FruitConf _data = new(); + + /// + /// Name returns the FruitConf's message name. + /// + public static string Name() => Protoconf.FruitConf.Descriptor.Name; + + /// + /// Load loads FruitConf's content in the given dir, based on format and messager options. + /// + public override bool Load(string dir, Format fmt, in Load.MessagerOptions? options = null) + { + var start = DateTime.Now; + try + { + _data = (Protoconf.FruitConf)( + Tableau.Load.LoadMessagerInDir(Protoconf.FruitConf.Descriptor, dir, fmt, options) + ?? throw new InvalidOperationException() + ); + } + catch (Exception ex) + { + if (string.IsNullOrEmpty(Util.GetErrMsg())) + { + Util.SetErrMsg($"failed to load FruitConf: {ex.Message}"); + } + return false; + } + LoadStats.Duration = DateTime.Now - start; + return ProcessAfterLoad(); + } + + /// + /// Data returns the FruitConf's inner message data. + /// + public ref readonly Protoconf.FruitConf Data() => ref _data; + + /// + /// Message returns the FruitConf's inner message data. + /// + public override pb::IMessage? Message() => _data; + + /// + /// ProcessAfterLoad runs after this messager is loaded. + /// + protected override bool ProcessAfterLoad() + { + // OrderedIndex init. + _orderedIndexItemMap.Clear(); + _orderedIndexItemMap1.Clear(); + foreach (var item1 in _data.FruitMap) + { + var k1 = item1.Key; + foreach (var item2 in item1.Value.ItemMap) + { + { + // OrderedIndex: Price + var key = item2.Value.Price; + { + var list = _orderedIndexItemMap.TryGetValue(key, out var existingList) ? + existingList : _orderedIndexItemMap[key] = []; + list.Add(item2.Value); + } + { + var map = _orderedIndexItemMap1.TryGetValue(k1, out var existingMap) ? + existingMap : _orderedIndexItemMap1[k1] = []; + var list = map.TryGetValue(key, out var existingList) ? + existingList : map[key] = []; + list.Add(item2.Value); + } + } + } + } + // OrderedIndex(sort): Price + Comparison orderedIndexItemMapComparison = (a, b) => + (a.Id).CompareTo((b.Id)); + foreach (var itemList in _orderedIndexItemMap.Values) + { + itemList.Sort(orderedIndexItemMapComparison); + } + foreach (var itemDict in _orderedIndexItemMap1.Values) + { + foreach (var itemList in itemDict.Values) + { + itemList.Sort(orderedIndexItemMapComparison); + } + } + return true; + } + + /// + /// Get1 finds value in the 1st-level map. + /// It will return null if the key is not found. + /// + public Protoconf.FruitConf.Types.Fruit? Get1(int fruitType) => + _data.FruitMap?.TryGetValue(fruitType, out var val) == true ? val : null; + + /// + /// Get2 finds value in the 2nd-level map. + /// It will return null if the key is not found. + /// + public Protoconf.FruitConf.Types.Fruit.Types.Item? Get2(int fruitType, int id) => + Get1(fruitType)?.ItemMap?.TryGetValue(id, out var val) == true ? val : null; + + // OrderedIndex: Price + + /// + /// FindItemMap finds the ordered index: key(Price) to value(Protoconf.FruitConf.Types.Fruit.Types.Item) sorted map. + /// One key may correspond to multiple values, which are represented by a list. + /// + public ref readonly OrderedIndex_ItemMap FindItemMap() => ref _orderedIndexItemMap; + + /// + /// FindItem finds a list of all values of the given key(s). + /// + public List? FindItem(int price) => + _orderedIndexItemMap.TryGetValue(price, out var value) ? value : null; + + /// + /// FindFirstItem finds the first value of the given key(s), + /// or null if no value found. + /// + public Protoconf.FruitConf.Types.Fruit.Types.Item? FindFirstItem(int price) => + FindItem(price)?.FirstOrDefault(); + + /// + /// FindItemMap1 finds the ordered index: key(Price) to value(Protoconf.FruitConf.Types.Fruit.Types.Item), + /// which is the upper 1st-level sorted map specified by (fruitType). + /// One key may correspond to multiple values, which are represented by a list. + /// + public OrderedIndex_ItemMap? FindItemMap1(int fruitType) => + _orderedIndexItemMap1.TryGetValue(fruitType, out var value) ? value : null; + + /// + /// FindItem1 finds a list of all values of the given key(s) in the upper 1st-level sorted map + /// specified by (fruitType). + /// + public List? FindItem1(int fruitType, int price) => + FindItemMap1(fruitType)?.TryGetValue(price, out var value) == true ? value : null; + + /// + /// FindFirstItem1 finds the first value of the given key(s) in the upper 1st-level sorted map + /// specified by (fruitType), or null if no value found. + /// + public Protoconf.FruitConf.Types.Fruit.Types.Item? FindFirstItem1(int fruitType, int price) => + FindItem1(fruitType, price)?.FirstOrDefault(); + } + + /// + /// Fruit2Conf is a wrapper around protobuf message Protoconf.Fruit2Conf. + /// + public class Fruit2Conf : Messager, IMessagerName + { + // Index types. + // Index: CountryName + public class Index_CountryMap : Dictionary>; + + private Index_CountryMap _indexCountryMap = []; + + // Index: CountryItemAttrName + public class Index_AttrMap : Dictionary>; + + private Index_AttrMap _indexAttrMap = []; + + private Dictionary _indexAttrMap1 = []; + + // OrderedIndex types. + // OrderedIndex: CountryItemPrice + public class OrderedIndex_ItemMap : SortedDictionary>; + + private OrderedIndex_ItemMap _orderedIndexItemMap = []; + + private Dictionary _orderedIndexItemMap1 = []; + + private Protoconf.Fruit2Conf _data = new(); + + /// + /// Name returns the Fruit2Conf's message name. + /// + public static string Name() => Protoconf.Fruit2Conf.Descriptor.Name; + + /// + /// Load loads Fruit2Conf's content in the given dir, based on format and messager options. + /// + public override bool Load(string dir, Format fmt, in Load.MessagerOptions? options = null) + { + var start = DateTime.Now; + try + { + _data = (Protoconf.Fruit2Conf)( + Tableau.Load.LoadMessagerInDir(Protoconf.Fruit2Conf.Descriptor, dir, fmt, options) + ?? throw new InvalidOperationException() + ); + } + catch (Exception ex) + { + if (string.IsNullOrEmpty(Util.GetErrMsg())) + { + Util.SetErrMsg($"failed to load Fruit2Conf: {ex.Message}"); + } + return false; + } + LoadStats.Duration = DateTime.Now - start; + return ProcessAfterLoad(); + } + + /// + /// Data returns the Fruit2Conf's inner message data. + /// + public ref readonly Protoconf.Fruit2Conf Data() => ref _data; + + /// + /// Message returns the Fruit2Conf's inner message data. + /// + public override pb::IMessage? Message() => _data; + + /// + /// ProcessAfterLoad runs after this messager is loaded. + /// + protected override bool ProcessAfterLoad() + { + // Index init. + _indexCountryMap.Clear(); + _indexAttrMap.Clear(); + _indexAttrMap1.Clear(); + foreach (var item1 in _data.FruitMap) + { + var k1 = item1.Key; + foreach (var item2 in item1.Value.CountryList) + { + { + // Index: CountryName + var key = item2.Name; + { + var list = _indexCountryMap.TryGetValue(key, out var existingList) ? + existingList : _indexCountryMap[key] = []; + list.Add(item2); + } + } + foreach (var item3 in item2.ItemMap) + { + foreach (var item4 in item3.Value.AttrList) + { + { + // Index: CountryItemAttrName + var key = item4.Name; + { + var list = _indexAttrMap.TryGetValue(key, out var existingList) ? + existingList : _indexAttrMap[key] = []; + list.Add(item4); + } + { + var map = _indexAttrMap1.TryGetValue(k1, out var existingMap) ? + existingMap : _indexAttrMap1[k1] = []; + var list = map.TryGetValue(key, out var existingList) ? + existingList : map[key] = []; + list.Add(item4); + } + } + } + } + } + } + // OrderedIndex init. + _orderedIndexItemMap.Clear(); + _orderedIndexItemMap1.Clear(); + foreach (var item1 in _data.FruitMap) + { + var k1 = item1.Key; + foreach (var item2 in item1.Value.CountryList) + { + foreach (var item3 in item2.ItemMap) + { + { + // OrderedIndex: CountryItemPrice + var key = item3.Value.Price; + { + var list = _orderedIndexItemMap.TryGetValue(key, out var existingList) ? + existingList : _orderedIndexItemMap[key] = []; + list.Add(item3.Value); + } + { + var map = _orderedIndexItemMap1.TryGetValue(k1, out var existingMap) ? + existingMap : _orderedIndexItemMap1[k1] = []; + var list = map.TryGetValue(key, out var existingList) ? + existingList : map[key] = []; + list.Add(item3.Value); + } + } + } + } + } + // OrderedIndex(sort): CountryItemPrice + Comparison orderedIndexItemMapComparison = (a, b) => + (a.Id).CompareTo((b.Id)); + foreach (var itemList in _orderedIndexItemMap.Values) + { + itemList.Sort(orderedIndexItemMapComparison); + } + foreach (var itemDict in _orderedIndexItemMap1.Values) + { + foreach (var itemList in itemDict.Values) + { + itemList.Sort(orderedIndexItemMapComparison); + } + } + return true; + } + + /// + /// Get1 finds value in the 1st-level map. + /// It will return null if the key is not found. + /// + public Protoconf.Fruit2Conf.Types.Fruit? Get1(int fruitType) => + _data.FruitMap?.TryGetValue(fruitType, out var val) == true ? val : null; + + // Index: CountryName + + /// + /// FindCountryMap finds the index: key(CountryName) to value(Protoconf.Fruit2Conf.Types.Fruit.Types.Country) map. + /// One key may correspond to multiple values, which are represented by a list. + /// + public ref readonly Index_CountryMap FindCountryMap() => ref _indexCountryMap; + + /// + /// FindCountry finds a list of all values of the given key(s). + /// + public List? FindCountry(string name) => + _indexCountryMap.TryGetValue(name, out var value) ? value : null; + + /// + /// FindFirstCountry finds the first value of the given key(s), + /// or null if no value found. + /// + public Protoconf.Fruit2Conf.Types.Fruit.Types.Country? FindFirstCountry(string name) => + FindCountry(name)?.FirstOrDefault(); + + // Index: CountryItemAttrName + + /// + /// FindAttrMap finds the index: key(CountryItemAttrName) to value(Protoconf.Fruit2Conf.Types.Fruit.Types.Country.Types.Item.Types.Attr) map. + /// One key may correspond to multiple values, which are represented by a list. + /// + public ref readonly Index_AttrMap FindAttrMap() => ref _indexAttrMap; + + /// + /// FindAttr finds a list of all values of the given key(s). + /// + public List? FindAttr(string name) => + _indexAttrMap.TryGetValue(name, out var value) ? value : null; + + /// + /// FindFirstAttr finds the first value of the given key(s), + /// or null if no value found. + /// + public Protoconf.Fruit2Conf.Types.Fruit.Types.Country.Types.Item.Types.Attr? FindFirstAttr(string name) => + FindAttr(name)?.FirstOrDefault(); + + /// + /// FindAttrMap1 finds the index: key(CountryItemAttrName) to value(Protoconf.Fruit2Conf.Types.Fruit.Types.Country.Types.Item.Types.Attr), + /// which is the upper 1st-level map specified by (fruitType). + /// One key may correspond to multiple values, which are represented by a list. + /// + public Index_AttrMap? FindAttrMap1(int fruitType) => + _indexAttrMap1.TryGetValue(fruitType, out var value) ? value : null; + + /// + /// FindAttr1 finds a list of all values of the given key(s) in the upper 1st-level map + /// specified by (fruitType). + /// + public List? FindAttr1(int fruitType, string name) => + FindAttrMap1(fruitType)?.TryGetValue(name, out var value) == true ? value : null; + + /// + /// FindFirstAttr1 finds the first value of the given key(s) in the upper 1st-level map + /// specified by (fruitType), or null if no value found. + /// + public Protoconf.Fruit2Conf.Types.Fruit.Types.Country.Types.Item.Types.Attr? FindFirstAttr1(int fruitType, string name) => + FindAttr1(fruitType, name)?.FirstOrDefault(); + + // OrderedIndex: CountryItemPrice + + /// + /// FindItemMap finds the ordered index: key(CountryItemPrice) to value(Protoconf.Fruit2Conf.Types.Fruit.Types.Country.Types.Item) sorted map. + /// One key may correspond to multiple values, which are represented by a list. + /// + public ref readonly OrderedIndex_ItemMap FindItemMap() => ref _orderedIndexItemMap; + + /// + /// FindItem finds a list of all values of the given key(s). + /// + public List? FindItem(int price) => + _orderedIndexItemMap.TryGetValue(price, out var value) ? value : null; + + /// + /// FindFirstItem finds the first value of the given key(s), + /// or null if no value found. + /// + public Protoconf.Fruit2Conf.Types.Fruit.Types.Country.Types.Item? FindFirstItem(int price) => + FindItem(price)?.FirstOrDefault(); + + /// + /// FindItemMap1 finds the ordered index: key(CountryItemPrice) to value(Protoconf.Fruit2Conf.Types.Fruit.Types.Country.Types.Item), + /// which is the upper 1st-level sorted map specified by (fruitType). + /// One key may correspond to multiple values, which are represented by a list. + /// + public OrderedIndex_ItemMap? FindItemMap1(int fruitType) => + _orderedIndexItemMap1.TryGetValue(fruitType, out var value) ? value : null; + + /// + /// FindItem1 finds a list of all values of the given key(s) in the upper 1st-level sorted map + /// specified by (fruitType). + /// + public List? FindItem1(int fruitType, int price) => + FindItemMap1(fruitType)?.TryGetValue(price, out var value) == true ? value : null; + + /// + /// FindFirstItem1 finds the first value of the given key(s) in the upper 1st-level sorted map + /// specified by (fruitType), or null if no value found. + /// + public Protoconf.Fruit2Conf.Types.Fruit.Types.Country.Types.Item? FindFirstItem1(int fruitType, int price) => + FindItem1(fruitType, price)?.FirstOrDefault(); + } + + /// + /// Fruit3Conf is a wrapper around protobuf message Protoconf.Fruit3Conf. + /// + public class Fruit3Conf : Messager, IMessagerName + { + // Index types. + // Index: CountryName + public class Index_CountryMap : Dictionary>; + + private Index_CountryMap _indexCountryMap = []; + + // Index: CountryItemAttrName + public class Index_AttrMap : Dictionary>; + + private Index_AttrMap _indexAttrMap = []; + + // OrderedIndex types. + // OrderedIndex: CountryItemPrice + public class OrderedIndex_ItemMap : SortedDictionary>; + + private OrderedIndex_ItemMap _orderedIndexItemMap = []; + + private Protoconf.Fruit3Conf _data = new(); + + /// + /// Name returns the Fruit3Conf's message name. + /// + public static string Name() => Protoconf.Fruit3Conf.Descriptor.Name; + + /// + /// Load loads Fruit3Conf's content in the given dir, based on format and messager options. + /// + public override bool Load(string dir, Format fmt, in Load.MessagerOptions? options = null) + { + var start = DateTime.Now; + try + { + _data = (Protoconf.Fruit3Conf)( + Tableau.Load.LoadMessagerInDir(Protoconf.Fruit3Conf.Descriptor, dir, fmt, options) + ?? throw new InvalidOperationException() + ); + } + catch (Exception ex) + { + if (string.IsNullOrEmpty(Util.GetErrMsg())) + { + Util.SetErrMsg($"failed to load Fruit3Conf: {ex.Message}"); + } + return false; + } + LoadStats.Duration = DateTime.Now - start; + return ProcessAfterLoad(); + } + + /// + /// Data returns the Fruit3Conf's inner message data. + /// + public ref readonly Protoconf.Fruit3Conf Data() => ref _data; + + /// + /// Message returns the Fruit3Conf's inner message data. + /// + public override pb::IMessage? Message() => _data; + + /// + /// ProcessAfterLoad runs after this messager is loaded. + /// + protected override bool ProcessAfterLoad() + { + // Index init. + _indexCountryMap.Clear(); + _indexAttrMap.Clear(); + foreach (var item1 in _data.FruitList) + { + foreach (var item2 in item1.CountryList) + { + { + // Index: CountryName + var key = item2.Name; + { + var list = _indexCountryMap.TryGetValue(key, out var existingList) ? + existingList : _indexCountryMap[key] = []; + list.Add(item2); + } + } + foreach (var item3 in item2.ItemMap) + { + foreach (var item4 in item3.Value.AttrList) + { + { + // Index: CountryItemAttrName + var key = item4.Name; + { + var list = _indexAttrMap.TryGetValue(key, out var existingList) ? + existingList : _indexAttrMap[key] = []; + list.Add(item4); + } + } + } + } + } + } + // OrderedIndex init. + _orderedIndexItemMap.Clear(); + foreach (var item1 in _data.FruitList) + { + foreach (var item2 in item1.CountryList) + { + foreach (var item3 in item2.ItemMap) + { + { + // OrderedIndex: CountryItemPrice + var key = item3.Value.Price; + { + var list = _orderedIndexItemMap.TryGetValue(key, out var existingList) ? + existingList : _orderedIndexItemMap[key] = []; + list.Add(item3.Value); + } + } + } + } + } + // OrderedIndex(sort): CountryItemPrice + Comparison orderedIndexItemMapComparison = (a, b) => + (a.Id).CompareTo((b.Id)); + foreach (var itemList in _orderedIndexItemMap.Values) + { + itemList.Sort(orderedIndexItemMapComparison); + } + return true; + } + + // Index: CountryName + + /// + /// FindCountryMap finds the index: key(CountryName) to value(Protoconf.Fruit3Conf.Types.Fruit.Types.Country) map. + /// One key may correspond to multiple values, which are represented by a list. + /// + public ref readonly Index_CountryMap FindCountryMap() => ref _indexCountryMap; + + /// + /// FindCountry finds a list of all values of the given key(s). + /// + public List? FindCountry(string name) => + _indexCountryMap.TryGetValue(name, out var value) ? value : null; + + /// + /// FindFirstCountry finds the first value of the given key(s), + /// or null if no value found. + /// + public Protoconf.Fruit3Conf.Types.Fruit.Types.Country? FindFirstCountry(string name) => + FindCountry(name)?.FirstOrDefault(); + + // Index: CountryItemAttrName + + /// + /// FindAttrMap finds the index: key(CountryItemAttrName) to value(Protoconf.Fruit3Conf.Types.Fruit.Types.Country.Types.Item.Types.Attr) map. + /// One key may correspond to multiple values, which are represented by a list. + /// + public ref readonly Index_AttrMap FindAttrMap() => ref _indexAttrMap; + + /// + /// FindAttr finds a list of all values of the given key(s). + /// + public List? FindAttr(string name) => + _indexAttrMap.TryGetValue(name, out var value) ? value : null; + + /// + /// FindFirstAttr finds the first value of the given key(s), + /// or null if no value found. + /// + public Protoconf.Fruit3Conf.Types.Fruit.Types.Country.Types.Item.Types.Attr? FindFirstAttr(string name) => + FindAttr(name)?.FirstOrDefault(); + + // OrderedIndex: CountryItemPrice + + /// + /// FindItemMap finds the ordered index: key(CountryItemPrice) to value(Protoconf.Fruit3Conf.Types.Fruit.Types.Country.Types.Item) sorted map. + /// One key may correspond to multiple values, which are represented by a list. + /// + public ref readonly OrderedIndex_ItemMap FindItemMap() => ref _orderedIndexItemMap; + + /// + /// FindItem finds a list of all values of the given key(s). + /// + public List? FindItem(int price) => + _orderedIndexItemMap.TryGetValue(price, out var value) ? value : null; + + /// + /// FindFirstItem finds the first value of the given key(s), + /// or null if no value found. + /// + public Protoconf.Fruit3Conf.Types.Fruit.Types.Country.Types.Item? FindFirstItem(int price) => + FindItem(price)?.FirstOrDefault(); + } + + /// + /// Fruit4Conf is a wrapper around protobuf message Protoconf.Fruit4Conf. + /// + public class Fruit4Conf : Messager, IMessagerName + { + + // LevelIndex keys. + public readonly struct LevelIndex_Fruit_CountryKey : IEquatable + { + public int FruitType { get; } + public int Id { get; } + + public LevelIndex_Fruit_CountryKey(int fruitType, int id) + { + FruitType = fruitType; + Id = id; + } + + public bool Equals(LevelIndex_Fruit_CountryKey other) => + (FruitType, Id).Equals((other.FruitType, other.Id)); + + public override int GetHashCode() => + (FruitType, Id).GetHashCode(); + } + + // Index types. + // Index: CountryName + public class Index_CountryMap : Dictionary>; + + private Index_CountryMap _indexCountryMap = []; + + private Dictionary _indexCountryMap1 = []; + + // Index: CountryItemAttrName + public class Index_AttrMap : Dictionary>; + + private Index_AttrMap _indexAttrMap = []; + + private Dictionary _indexAttrMap1 = []; + + private Dictionary _indexAttrMap2 = []; + + // OrderedIndex types. + // OrderedIndex: CountryItemPrice + public class OrderedIndex_ItemMap : SortedDictionary>; + + private OrderedIndex_ItemMap _orderedIndexItemMap = []; + + private Dictionary _orderedIndexItemMap1 = []; + + private Dictionary _orderedIndexItemMap2 = []; + + private Protoconf.Fruit4Conf _data = new(); + + /// + /// Name returns the Fruit4Conf's message name. + /// + public static string Name() => Protoconf.Fruit4Conf.Descriptor.Name; + + /// + /// Load loads Fruit4Conf's content in the given dir, based on format and messager options. + /// + public override bool Load(string dir, Format fmt, in Load.MessagerOptions? options = null) + { + var start = DateTime.Now; + try + { + _data = (Protoconf.Fruit4Conf)( + Tableau.Load.LoadMessagerInDir(Protoconf.Fruit4Conf.Descriptor, dir, fmt, options) + ?? throw new InvalidOperationException() + ); + } + catch (Exception ex) + { + if (string.IsNullOrEmpty(Util.GetErrMsg())) + { + Util.SetErrMsg($"failed to load Fruit4Conf: {ex.Message}"); + } + return false; + } + LoadStats.Duration = DateTime.Now - start; + return ProcessAfterLoad(); + } + + /// + /// Data returns the Fruit4Conf's inner message data. + /// + public ref readonly Protoconf.Fruit4Conf Data() => ref _data; + + /// + /// Message returns the Fruit4Conf's inner message data. + /// + public override pb::IMessage? Message() => _data; + + /// + /// ProcessAfterLoad runs after this messager is loaded. + /// + protected override bool ProcessAfterLoad() + { + // Index init. + _indexCountryMap.Clear(); + _indexCountryMap1.Clear(); + _indexAttrMap.Clear(); + _indexAttrMap1.Clear(); + _indexAttrMap2.Clear(); + foreach (var item1 in _data.FruitMap) + { + var k1 = item1.Key; + foreach (var item2 in item1.Value.CountryMap) + { + var k2 = item2.Key; + { + // Index: CountryName + var key = item2.Value.Name; + { + var list = _indexCountryMap.TryGetValue(key, out var existingList) ? + existingList : _indexCountryMap[key] = []; + list.Add(item2.Value); + } + { + var map = _indexCountryMap1.TryGetValue(k1, out var existingMap) ? + existingMap : _indexCountryMap1[k1] = []; + var list = map.TryGetValue(key, out var existingList) ? + existingList : map[key] = []; + list.Add(item2.Value); + } + } + foreach (var item3 in item2.Value.ItemMap) + { + foreach (var item4 in item3.Value.AttrList) + { + { + // Index: CountryItemAttrName + var key = item4.Name; + { + var list = _indexAttrMap.TryGetValue(key, out var existingList) ? + existingList : _indexAttrMap[key] = []; + list.Add(item4); + } + { + var map = _indexAttrMap1.TryGetValue(k1, out var existingMap) ? + existingMap : _indexAttrMap1[k1] = []; + var list = map.TryGetValue(key, out var existingList) ? + existingList : map[key] = []; + list.Add(item4); + } + { + var mapKey = new LevelIndex_Fruit_CountryKey(k1, k2); + var map = _indexAttrMap2.TryGetValue(mapKey, out var existingMap) ? + existingMap : _indexAttrMap2[mapKey] = []; + var list = map.TryGetValue(key, out var existingList) ? + existingList : map[key] = []; + list.Add(item4); + } + } + } + } + } + } + // OrderedIndex init. + _orderedIndexItemMap.Clear(); + _orderedIndexItemMap1.Clear(); + _orderedIndexItemMap2.Clear(); + foreach (var item1 in _data.FruitMap) + { + var k1 = item1.Key; + foreach (var item2 in item1.Value.CountryMap) + { + var k2 = item2.Key; + foreach (var item3 in item2.Value.ItemMap) + { + { + // OrderedIndex: CountryItemPrice + var key = item3.Value.Price; + { + var list = _orderedIndexItemMap.TryGetValue(key, out var existingList) ? + existingList : _orderedIndexItemMap[key] = []; + list.Add(item3.Value); + } + { + var map = _orderedIndexItemMap1.TryGetValue(k1, out var existingMap) ? + existingMap : _orderedIndexItemMap1[k1] = []; + var list = map.TryGetValue(key, out var existingList) ? + existingList : map[key] = []; + list.Add(item3.Value); + } + { + var mapKey = new LevelIndex_Fruit_CountryKey(k1, k2); + var map = _orderedIndexItemMap2.TryGetValue(mapKey, out var existingMap) ? + existingMap : _orderedIndexItemMap2[mapKey] = []; + var list = map.TryGetValue(key, out var existingList) ? + existingList : map[key] = []; + list.Add(item3.Value); + } + } + } + } + } + // OrderedIndex(sort): CountryItemPrice + Comparison orderedIndexItemMapComparison = (a, b) => + (a.Id).CompareTo((b.Id)); + foreach (var itemList in _orderedIndexItemMap.Values) + { + itemList.Sort(orderedIndexItemMapComparison); + } + foreach (var itemDict in _orderedIndexItemMap1.Values) + { + foreach (var itemList in itemDict.Values) + { + itemList.Sort(orderedIndexItemMapComparison); + } + } + foreach (var itemDict in _orderedIndexItemMap2.Values) + { + foreach (var itemList in itemDict.Values) + { + itemList.Sort(orderedIndexItemMapComparison); + } + } + return true; + } + + /// + /// Get1 finds value in the 1st-level map. + /// It will return null if the key is not found. + /// + public Protoconf.Fruit4Conf.Types.Fruit? Get1(int fruitType) => + _data.FruitMap?.TryGetValue(fruitType, out var val) == true ? val : null; + + /// + /// Get2 finds value in the 2nd-level map. + /// It will return null if the key is not found. + /// + public Protoconf.Fruit4Conf.Types.Fruit.Types.Country? Get2(int fruitType, int id) => + Get1(fruitType)?.CountryMap?.TryGetValue(id, out var val) == true ? val : null; + + /// + /// Get3 finds value in the 3rd-level map. + /// It will return null if the key is not found. + /// + public Protoconf.Fruit4Conf.Types.Fruit.Types.Country.Types.Item? Get3(int fruitType, int id, int id3) => + Get2(fruitType, id)?.ItemMap?.TryGetValue(id3, out var val) == true ? val : null; + + // Index: CountryName + + /// + /// FindCountryMap finds the index: key(CountryName) to value(Protoconf.Fruit4Conf.Types.Fruit.Types.Country) map. + /// One key may correspond to multiple values, which are represented by a list. + /// + public ref readonly Index_CountryMap FindCountryMap() => ref _indexCountryMap; + + /// + /// FindCountry finds a list of all values of the given key(s). + /// + public List? FindCountry(string name) => + _indexCountryMap.TryGetValue(name, out var value) ? value : null; + + /// + /// FindFirstCountry finds the first value of the given key(s), + /// or null if no value found. + /// + public Protoconf.Fruit4Conf.Types.Fruit.Types.Country? FindFirstCountry(string name) => + FindCountry(name)?.FirstOrDefault(); + + /// + /// FindCountryMap1 finds the index: key(CountryName) to value(Protoconf.Fruit4Conf.Types.Fruit.Types.Country), + /// which is the upper 1st-level map specified by (fruitType). + /// One key may correspond to multiple values, which are represented by a list. + /// + public Index_CountryMap? FindCountryMap1(int fruitType) => + _indexCountryMap1.TryGetValue(fruitType, out var value) ? value : null; + + /// + /// FindCountry1 finds a list of all values of the given key(s) in the upper 1st-level map + /// specified by (fruitType). + /// + public List? FindCountry1(int fruitType, string name) => + FindCountryMap1(fruitType)?.TryGetValue(name, out var value) == true ? value : null; + + /// + /// FindFirstCountry1 finds the first value of the given key(s) in the upper 1st-level map + /// specified by (fruitType), or null if no value found. + /// + public Protoconf.Fruit4Conf.Types.Fruit.Types.Country? FindFirstCountry1(int fruitType, string name) => + FindCountry1(fruitType, name)?.FirstOrDefault(); + + // Index: CountryItemAttrName + + /// + /// FindAttrMap finds the index: key(CountryItemAttrName) to value(Protoconf.Fruit4Conf.Types.Fruit.Types.Country.Types.Item.Types.Attr) map. + /// One key may correspond to multiple values, which are represented by a list. + /// + public ref readonly Index_AttrMap FindAttrMap() => ref _indexAttrMap; + + /// + /// FindAttr finds a list of all values of the given key(s). + /// + public List? FindAttr(string name) => + _indexAttrMap.TryGetValue(name, out var value) ? value : null; + + /// + /// FindFirstAttr finds the first value of the given key(s), + /// or null if no value found. + /// + public Protoconf.Fruit4Conf.Types.Fruit.Types.Country.Types.Item.Types.Attr? FindFirstAttr(string name) => + FindAttr(name)?.FirstOrDefault(); + + /// + /// FindAttrMap1 finds the index: key(CountryItemAttrName) to value(Protoconf.Fruit4Conf.Types.Fruit.Types.Country.Types.Item.Types.Attr), + /// which is the upper 1st-level map specified by (fruitType). + /// One key may correspond to multiple values, which are represented by a list. + /// + public Index_AttrMap? FindAttrMap1(int fruitType) => + _indexAttrMap1.TryGetValue(fruitType, out var value) ? value : null; + + /// + /// FindAttr1 finds a list of all values of the given key(s) in the upper 1st-level map + /// specified by (fruitType). + /// + public List? FindAttr1(int fruitType, string name) => + FindAttrMap1(fruitType)?.TryGetValue(name, out var value) == true ? value : null; + + /// + /// FindFirstAttr1 finds the first value of the given key(s) in the upper 1st-level map + /// specified by (fruitType), or null if no value found. + /// + public Protoconf.Fruit4Conf.Types.Fruit.Types.Country.Types.Item.Types.Attr? FindFirstAttr1(int fruitType, string name) => + FindAttr1(fruitType, name)?.FirstOrDefault(); + + /// + /// FindAttrMap2 finds the index: key(CountryItemAttrName) to value(Protoconf.Fruit4Conf.Types.Fruit.Types.Country.Types.Item.Types.Attr), + /// which is the upper 2nd-level map specified by (fruitType, id). + /// One key may correspond to multiple values, which are represented by a list. + /// + public Index_AttrMap? FindAttrMap2(int fruitType, int id) => + _indexAttrMap2.TryGetValue(new LevelIndex_Fruit_CountryKey(fruitType, id), out var value) ? value : null; + + /// + /// FindAttr2 finds a list of all values of the given key(s) in the upper 2nd-level map + /// specified by (fruitType, id). + /// + public List? FindAttr2(int fruitType, int id, string name) => + FindAttrMap2(fruitType, id)?.TryGetValue(name, out var value) == true ? value : null; + + /// + /// FindFirstAttr2 finds the first value of the given key(s) in the upper 2nd-level map + /// specified by (fruitType, id), or null if no value found. + /// + public Protoconf.Fruit4Conf.Types.Fruit.Types.Country.Types.Item.Types.Attr? FindFirstAttr2(int fruitType, int id, string name) => + FindAttr2(fruitType, id, name)?.FirstOrDefault(); + + // OrderedIndex: CountryItemPrice + + /// + /// FindItemMap finds the ordered index: key(CountryItemPrice) to value(Protoconf.Fruit4Conf.Types.Fruit.Types.Country.Types.Item) sorted map. + /// One key may correspond to multiple values, which are represented by a list. + /// + public ref readonly OrderedIndex_ItemMap FindItemMap() => ref _orderedIndexItemMap; + + /// + /// FindItem finds a list of all values of the given key(s). + /// + public List? FindItem(int price) => + _orderedIndexItemMap.TryGetValue(price, out var value) ? value : null; + + /// + /// FindFirstItem finds the first value of the given key(s), + /// or null if no value found. + /// + public Protoconf.Fruit4Conf.Types.Fruit.Types.Country.Types.Item? FindFirstItem(int price) => + FindItem(price)?.FirstOrDefault(); + + /// + /// FindItemMap1 finds the ordered index: key(CountryItemPrice) to value(Protoconf.Fruit4Conf.Types.Fruit.Types.Country.Types.Item), + /// which is the upper 1st-level sorted map specified by (fruitType). + /// One key may correspond to multiple values, which are represented by a list. + /// + public OrderedIndex_ItemMap? FindItemMap1(int fruitType) => + _orderedIndexItemMap1.TryGetValue(fruitType, out var value) ? value : null; + + /// + /// FindItem1 finds a list of all values of the given key(s) in the upper 1st-level sorted map + /// specified by (fruitType). + /// + public List? FindItem1(int fruitType, int price) => + FindItemMap1(fruitType)?.TryGetValue(price, out var value) == true ? value : null; + + /// + /// FindFirstItem1 finds the first value of the given key(s) in the upper 1st-level sorted map + /// specified by (fruitType), or null if no value found. + /// + public Protoconf.Fruit4Conf.Types.Fruit.Types.Country.Types.Item? FindFirstItem1(int fruitType, int price) => + FindItem1(fruitType, price)?.FirstOrDefault(); + + /// + /// FindItemMap2 finds the ordered index: key(CountryItemPrice) to value(Protoconf.Fruit4Conf.Types.Fruit.Types.Country.Types.Item), + /// which is the upper 2nd-level sorted map specified by (fruitType, id). + /// One key may correspond to multiple values, which are represented by a list. + /// + public OrderedIndex_ItemMap? FindItemMap2(int fruitType, int id) => + _orderedIndexItemMap2.TryGetValue(new LevelIndex_Fruit_CountryKey(fruitType, id), out var value) ? value : null; + + /// + /// FindItem2 finds a list of all values of the given key(s) in the upper 2nd-level sorted map + /// specified by (fruitType, id). + /// + public List? FindItem2(int fruitType, int id, int price) => + FindItemMap2(fruitType, id)?.TryGetValue(price, out var value) == true ? value : null; + + /// + /// FindFirstItem2 finds the first value of the given key(s) in the upper 2nd-level sorted map + /// specified by (fruitType, id), or null if no value found. + /// + public Protoconf.Fruit4Conf.Types.Fruit.Types.Country.Types.Item? FindFirstItem2(int fruitType, int id, int price) => + FindItem2(fruitType, id, price)?.FirstOrDefault(); + } + + /// + /// Fruit5Conf is a wrapper around protobuf message Protoconf.Fruit5Conf. + /// + public class Fruit5Conf : Messager, IMessagerName + { + // Index types. + // Index: CountryName + public class Index_CountryMap : Dictionary>; + + private Index_CountryMap _indexCountryMap = []; + + private Dictionary _indexCountryMap1 = []; + + private Protoconf.Fruit5Conf _data = new(); + + /// + /// Name returns the Fruit5Conf's message name. + /// + public static string Name() => Protoconf.Fruit5Conf.Descriptor.Name; + + /// + /// Load loads Fruit5Conf's content in the given dir, based on format and messager options. + /// + public override bool Load(string dir, Format fmt, in Load.MessagerOptions? options = null) + { + var start = DateTime.Now; + try + { + _data = (Protoconf.Fruit5Conf)( + Tableau.Load.LoadMessagerInDir(Protoconf.Fruit5Conf.Descriptor, dir, fmt, options) + ?? throw new InvalidOperationException() + ); + } + catch (Exception ex) + { + if (string.IsNullOrEmpty(Util.GetErrMsg())) + { + Util.SetErrMsg($"failed to load Fruit5Conf: {ex.Message}"); + } + return false; + } + LoadStats.Duration = DateTime.Now - start; + return ProcessAfterLoad(); + } + + /// + /// Data returns the Fruit5Conf's inner message data. + /// + public ref readonly Protoconf.Fruit5Conf Data() => ref _data; + + /// + /// Message returns the Fruit5Conf's inner message data. + /// + public override pb::IMessage? Message() => _data; + + /// + /// ProcessAfterLoad runs after this messager is loaded. + /// + protected override bool ProcessAfterLoad() + { + // Index init. + _indexCountryMap.Clear(); + _indexCountryMap1.Clear(); + foreach (var item1 in _data.FruitMap) + { + var k1 = item1.Key; + foreach (var item2 in item1.Value.CountryMap) + { + { + // Index: CountryName + var key = item2.Value.Name; + { + var list = _indexCountryMap.TryGetValue(key, out var existingList) ? + existingList : _indexCountryMap[key] = []; + list.Add(item2.Value); + } + { + var map = _indexCountryMap1.TryGetValue(k1, out var existingMap) ? + existingMap : _indexCountryMap1[k1] = []; + var list = map.TryGetValue(key, out var existingList) ? + existingList : map[key] = []; + list.Add(item2.Value); + } + } + } + } + return true; + } + + /// + /// Get1 finds value in the 1st-level map. + /// It will return null if the key is not found. + /// + public Protoconf.Fruit5Conf.Types.Fruit? Get1(int fruitType) => + _data.FruitMap?.TryGetValue(fruitType, out var val) == true ? val : null; + + /// + /// Get2 finds value in the 2nd-level map. + /// It will return null if the key is not found. + /// + public Protoconf.Fruit5Conf.Types.Fruit.Types.Country? Get2(int fruitType, int id) => + Get1(fruitType)?.CountryMap?.TryGetValue(id, out var val) == true ? val : null; + + /// + /// Get3 finds value in the 3rd-level map. + /// It will return null if the key is not found. + /// + public Protoconf.Fruit5Conf.Types.Fruit.Types.Country.Types.Item? Get3(int fruitType, int id, int id3) => + Get2(fruitType, id)?.ItemMap?.TryGetValue(id3, out var val) == true ? val : null; + + // Index: CountryName + + /// + /// FindCountryMap finds the index: key(CountryName) to value(Protoconf.Fruit5Conf.Types.Fruit.Types.Country) map. + /// One key may correspond to multiple values, which are represented by a list. + /// + public ref readonly Index_CountryMap FindCountryMap() => ref _indexCountryMap; + + /// + /// FindCountry finds a list of all values of the given key(s). + /// + public List? FindCountry(string name) => + _indexCountryMap.TryGetValue(name, out var value) ? value : null; + + /// + /// FindFirstCountry finds the first value of the given key(s), + /// or null if no value found. + /// + public Protoconf.Fruit5Conf.Types.Fruit.Types.Country? FindFirstCountry(string name) => + FindCountry(name)?.FirstOrDefault(); + + /// + /// FindCountryMap1 finds the index: key(CountryName) to value(Protoconf.Fruit5Conf.Types.Fruit.Types.Country), + /// which is the upper 1st-level map specified by (fruitType). + /// One key may correspond to multiple values, which are represented by a list. + /// + public Index_CountryMap? FindCountryMap1(int fruitType) => + _indexCountryMap1.TryGetValue(fruitType, out var value) ? value : null; + + /// + /// FindCountry1 finds a list of all values of the given key(s) in the upper 1st-level map + /// specified by (fruitType). + /// + public List? FindCountry1(int fruitType, string name) => + FindCountryMap1(fruitType)?.TryGetValue(name, out var value) == true ? value : null; + + /// + /// FindFirstCountry1 finds the first value of the given key(s) in the upper 1st-level map + /// specified by (fruitType), or null if no value found. + /// + public Protoconf.Fruit5Conf.Types.Fruit.Types.Country? FindFirstCountry1(int fruitType, string name) => + FindCountry1(fruitType, name)?.FirstOrDefault(); + } +} diff --git a/test/csharp-tableau-loader/tableau/ItemConf.pc.cs b/test/csharp-tableau-loader/tableau/ItemConf.pc.cs new file mode 100644 index 00000000..42497658 --- /dev/null +++ b/test/csharp-tableau-loader/tableau/ItemConf.pc.cs @@ -0,0 +1,630 @@ +// +// Code generated by protoc-gen-csharp-tableau-loader. DO NOT EDIT. +// versions: +// - protoc-gen-csharp-tableau-loader v0.1.0 +// - protoc v3.19.3 +// source: item_conf.proto +// +#nullable enable +using pb = global::Google.Protobuf; +namespace Tableau +{ + /// + /// ItemConf is a wrapper around protobuf message Protoconf.ItemConf. + /// + public class ItemConf : Messager, IMessagerName + { + // OrderedMap types. + public class OrderedMap_ItemMap : SortedDictionary; + + private OrderedMap_ItemMap _orderedMap = []; + + // Index types. + // Index: Type + public class Index_ItemMap : Dictionary>; + + private Index_ItemMap _indexItemMap = []; + + // Index: Param@ItemInfo + public class Index_ItemInfoMap : Dictionary>; + + private Index_ItemInfoMap _indexItemInfoMap = []; + + // Index: Default@ItemDefaultInfo + public class Index_ItemDefaultInfoMap : Dictionary>; + + private Index_ItemDefaultInfoMap _indexItemDefaultInfoMap = []; + + // Index: ExtType@ItemExtInfo + public class Index_ItemExtInfoMap : Dictionary>; + + private Index_ItemExtInfoMap _indexItemExtInfoMap = []; + + // Index: (ID,Name)@AwardItem + public readonly struct Index_AwardItemKey : IEquatable + { + public uint Id { get; } + public string Name { get; } + + public Index_AwardItemKey(uint id, string name) + { + Id = id; + Name = name; + } + + public bool Equals(Index_AwardItemKey other) => + (Id, Name).Equals((other.Id, other.Name)); + + public override int GetHashCode() => + (Id, Name).GetHashCode(); + } + + public class Index_AwardItemMap : Dictionary>; + + private Index_AwardItemMap _indexAwardItemMap = []; + + // Index: (ID,Type,Param,ExtType)@SpecialItem + public readonly struct Index_SpecialItemKey : IEquatable + { + public uint Id { get; } + public Protoconf.FruitType Type { get; } + public int Param { get; } + public Protoconf.FruitType ExtType { get; } + + public Index_SpecialItemKey(uint id, Protoconf.FruitType type, int param, Protoconf.FruitType extType) + { + Id = id; + Type = type; + Param = param; + ExtType = extType; + } + + public bool Equals(Index_SpecialItemKey other) => + (Id, Type, Param, ExtType).Equals((other.Id, other.Type, other.Param, other.ExtType)); + + public override int GetHashCode() => + (Id, Type, Param, ExtType).GetHashCode(); + } + + public class Index_SpecialItemMap : Dictionary>; + + private Index_SpecialItemMap _indexSpecialItemMap = []; + + // Index: PathDir@ItemPathDir + public class Index_ItemPathDirMap : Dictionary>; + + private Index_ItemPathDirMap _indexItemPathDirMap = []; + + // Index: PathName@ItemPathName + public class Index_ItemPathNameMap : Dictionary>; + + private Index_ItemPathNameMap _indexItemPathNameMap = []; + + // Index: PathFriendID@ItemPathFriendID + public class Index_ItemPathFriendIDMap : Dictionary>; + + private Index_ItemPathFriendIDMap _indexItemPathFriendIdMap = []; + + // Index: UseEffectType@UseEffectType + public class Index_UseEffectTypeMap : Dictionary>; + + private Index_UseEffectTypeMap _indexUseEffectTypeMap = []; + + // OrderedIndex types. + // OrderedIndex: ExtType@ExtType + public class OrderedIndex_ExtTypeMap : SortedDictionary>; + + private OrderedIndex_ExtTypeMap _orderedIndexExtTypeMap = []; + + // OrderedIndex: (Param,ExtType)@ParamExtType + public readonly struct OrderedIndex_ParamExtTypeKey : IComparable + { + public int Param { get; } + public Protoconf.FruitType ExtType { get; } + + public OrderedIndex_ParamExtTypeKey(int param, Protoconf.FruitType extType) + { + Param = param; + ExtType = extType; + } + + public int CompareTo(OrderedIndex_ParamExtTypeKey other) => + (Param, ExtType).CompareTo((other.Param, other.ExtType)); + } + + public class OrderedIndex_ParamExtTypeMap : SortedDictionary>; + + private OrderedIndex_ParamExtTypeMap _orderedIndexParamExtTypeMap = []; + + private Protoconf.ItemConf _data = new(); + + /// + /// Name returns the ItemConf's message name. + /// + public static string Name() => Protoconf.ItemConf.Descriptor.Name; + + /// + /// Load loads ItemConf's content in the given dir, based on format and messager options. + /// + public override bool Load(string dir, Format fmt, in Load.MessagerOptions? options = null) + { + var start = DateTime.Now; + try + { + _data = (Protoconf.ItemConf)( + Tableau.Load.LoadMessagerInDir(Protoconf.ItemConf.Descriptor, dir, fmt, options) + ?? throw new InvalidOperationException() + ); + } + catch (Exception ex) + { + if (string.IsNullOrEmpty(Util.GetErrMsg())) + { + Util.SetErrMsg($"failed to load ItemConf: {ex.Message}"); + } + return false; + } + LoadStats.Duration = DateTime.Now - start; + return ProcessAfterLoad(); + } + + /// + /// Data returns the ItemConf's inner message data. + /// + public ref readonly Protoconf.ItemConf Data() => ref _data; + + /// + /// Message returns the ItemConf's inner message data. + /// + public override pb::IMessage? Message() => _data; + + /// + /// ProcessAfterLoad runs after this messager is loaded. + /// + protected override bool ProcessAfterLoad() + { + // OrderedMap init. + _orderedMap.Clear(); + foreach (var (key1, value1) in _data.ItemMap) + { + _orderedMap[key1] = value1; + } + // Index init. + _indexItemMap.Clear(); + _indexItemInfoMap.Clear(); + _indexItemDefaultInfoMap.Clear(); + _indexItemExtInfoMap.Clear(); + _indexAwardItemMap.Clear(); + _indexSpecialItemMap.Clear(); + _indexItemPathDirMap.Clear(); + _indexItemPathNameMap.Clear(); + _indexItemPathFriendIdMap.Clear(); + _indexUseEffectTypeMap.Clear(); + foreach (var item1 in _data.ItemMap) + { + { + // Index: Type + var key = item1.Value.Type; + { + var list = _indexItemMap.TryGetValue(key, out var existingList) ? + existingList : _indexItemMap[key] = []; + list.Add(item1.Value); + } + } + { + // Index: Param@ItemInfo + foreach (var item2 in item1.Value.ParamList ?? Enumerable.Empty()) + { + { + var list = _indexItemInfoMap.TryGetValue(item2, out var existingList) ? + existingList : _indexItemInfoMap[item2] = []; + list.Add(item1.Value); + } + } + } + { + // Index: Default@ItemDefaultInfo + var key = item1.Value.Default; + { + var list = _indexItemDefaultInfoMap.TryGetValue(key, out var existingList) ? + existingList : _indexItemDefaultInfoMap[key] = []; + list.Add(item1.Value); + } + } + { + // Index: ExtType@ItemExtInfo + foreach (var item2 in item1.Value.ExtTypeList ?? Enumerable.Empty()) + { + { + var list = _indexItemExtInfoMap.TryGetValue(item2, out var existingList) ? + existingList : _indexItemExtInfoMap[item2] = []; + list.Add(item1.Value); + } + } + } + { + // Index: (ID,Name)@AwardItem + var key = new Index_AwardItemKey(item1.Value.Id, item1.Value.Name); + { + var list = _indexAwardItemMap.TryGetValue(key, out var existingList) ? + existingList : _indexAwardItemMap[key] = []; + list.Add(item1.Value); + } + } + { + // Index: (ID,Type,Param,ExtType)@SpecialItem + foreach (var indexItem2 in item1.Value.ParamList ?? Enumerable.Empty()) + { + foreach (var indexItem3 in item1.Value.ExtTypeList ?? Enumerable.Empty()) + { + var key = new Index_SpecialItemKey(item1.Value.Id, item1.Value.Type, indexItem2, indexItem3); + { + var list = _indexSpecialItemMap.TryGetValue(key, out var existingList) ? + existingList : _indexSpecialItemMap[key] = []; + list.Add(item1.Value); + } + } + } + } + { + // Index: PathDir@ItemPathDir + var key = item1.Value.Path?.Dir ?? ""; + { + var list = _indexItemPathDirMap.TryGetValue(key, out var existingList) ? + existingList : _indexItemPathDirMap[key] = []; + list.Add(item1.Value); + } + } + { + // Index: PathName@ItemPathName + foreach (var item2 in item1.Value.Path?.NameList ?? Enumerable.Empty()) + { + { + var list = _indexItemPathNameMap.TryGetValue(item2, out var existingList) ? + existingList : _indexItemPathNameMap[item2] = []; + list.Add(item1.Value); + } + } + } + { + // Index: PathFriendID@ItemPathFriendID + var key = item1.Value.Path?.Friend?.Id ?? 0; + { + var list = _indexItemPathFriendIdMap.TryGetValue(key, out var existingList) ? + existingList : _indexItemPathFriendIdMap[key] = []; + list.Add(item1.Value); + } + } + { + // Index: UseEffectType@UseEffectType + var key = item1.Value.UseEffect?.Type ?? 0; + { + var list = _indexUseEffectTypeMap.TryGetValue(key, out var existingList) ? + existingList : _indexUseEffectTypeMap[key] = []; + list.Add(item1.Value); + } + } + } + // Index(sort): Param@ItemInfo + Comparison indexItemInfoMapComparison = (a, b) => + (a.Id).CompareTo((b.Id)); + foreach (var itemList in _indexItemInfoMap.Values) + { + itemList.Sort(indexItemInfoMapComparison); + } + // Index(sort): (ID,Name)@AwardItem + Comparison indexAwardItemMapComparison = (a, b) => + (a.Type, a.UseEffect?.Type ?? 0).CompareTo((b.Type, b.UseEffect?.Type ?? 0)); + foreach (var itemList in _indexAwardItemMap.Values) + { + itemList.Sort(indexAwardItemMapComparison); + } + // OrderedIndex init. + _orderedIndexExtTypeMap.Clear(); + _orderedIndexParamExtTypeMap.Clear(); + foreach (var item1 in _data.ItemMap) + { + { + // OrderedIndex: ExtType@ExtType + foreach (var item2 in item1.Value.ExtTypeList ?? Enumerable.Empty()) + { + var key = item2; + { + var list = _orderedIndexExtTypeMap.TryGetValue(key, out var existingList) ? + existingList : _orderedIndexExtTypeMap[key] = []; + list.Add(item1.Value); + } + } + } + { + // OrderedIndex: (Param,ExtType)@ParamExtType + foreach (var indexItem0 in item1.Value.ParamList ?? Enumerable.Empty()) + { + foreach (var indexItem1 in item1.Value.ExtTypeList ?? Enumerable.Empty()) + { + var key = new OrderedIndex_ParamExtTypeKey(indexItem0, indexItem1); + { + var list = _orderedIndexParamExtTypeMap.TryGetValue(key, out var existingList) ? + existingList : _orderedIndexParamExtTypeMap[key] = []; + list.Add(item1.Value); + } + } + } + } + } + // OrderedIndex(sort): (Param,ExtType)@ParamExtType + Comparison orderedIndexParamExtTypeMapComparison = (a, b) => + (a.Id).CompareTo((b.Id)); + foreach (var itemList in _orderedIndexParamExtTypeMap.Values) + { + itemList.Sort(orderedIndexParamExtTypeMapComparison); + } + return true; + } + + /// + /// Get1 finds value in the 1st-level map. + /// It will return null if the key is not found. + /// + public Protoconf.ItemConf.Types.Item? Get1(uint id) => + _data.ItemMap?.TryGetValue(id, out var val) == true ? val : null; + + // OrderedMap accessers. + /// + /// GetOrderedMap returns the 1st-level ordered map. + /// + public ref readonly OrderedMap_ItemMap GetOrderedMap() => ref _orderedMap; + + // Index: Type + + /// + /// FindItemMap finds the index: key(Type) to value(Protoconf.ItemConf.Types.Item) map. + /// One key may correspond to multiple values, which are represented by a list. + /// + public ref readonly Index_ItemMap FindItemMap() => ref _indexItemMap; + + /// + /// FindItem finds a list of all values of the given key(s). + /// + public List? FindItem(Protoconf.FruitType type) => + _indexItemMap.TryGetValue(type, out var value) ? value : null; + + /// + /// FindFirstItem finds the first value of the given key(s), + /// or null if no value found. + /// + public Protoconf.ItemConf.Types.Item? FindFirstItem(Protoconf.FruitType type) => + FindItem(type)?.FirstOrDefault(); + + // Index: Param@ItemInfo + + /// + /// FindItemInfoMap finds the index: key(Param@ItemInfo) to value(Protoconf.ItemConf.Types.Item) map. + /// One key may correspond to multiple values, which are represented by a list. + /// + public ref readonly Index_ItemInfoMap FindItemInfoMap() => ref _indexItemInfoMap; + + /// + /// FindItemInfo finds a list of all values of the given key(s). + /// + public List? FindItemInfo(int param) => + _indexItemInfoMap.TryGetValue(param, out var value) ? value : null; + + /// + /// FindFirstItemInfo finds the first value of the given key(s), + /// or null if no value found. + /// + public Protoconf.ItemConf.Types.Item? FindFirstItemInfo(int param) => + FindItemInfo(param)?.FirstOrDefault(); + + // Index: Default@ItemDefaultInfo + + /// + /// FindItemDefaultInfoMap finds the index: key(Default@ItemDefaultInfo) to value(Protoconf.ItemConf.Types.Item) map. + /// One key may correspond to multiple values, which are represented by a list. + /// + public ref readonly Index_ItemDefaultInfoMap FindItemDefaultInfoMap() => ref _indexItemDefaultInfoMap; + + /// + /// FindItemDefaultInfo finds a list of all values of the given key(s). + /// + public List? FindItemDefaultInfo(string default_) => + _indexItemDefaultInfoMap.TryGetValue(default_, out var value) ? value : null; + + /// + /// FindFirstItemDefaultInfo finds the first value of the given key(s), + /// or null if no value found. + /// + public Protoconf.ItemConf.Types.Item? FindFirstItemDefaultInfo(string default_) => + FindItemDefaultInfo(default_)?.FirstOrDefault(); + + // Index: ExtType@ItemExtInfo + + /// + /// FindItemExtInfoMap finds the index: key(ExtType@ItemExtInfo) to value(Protoconf.ItemConf.Types.Item) map. + /// One key may correspond to multiple values, which are represented by a list. + /// + public ref readonly Index_ItemExtInfoMap FindItemExtInfoMap() => ref _indexItemExtInfoMap; + + /// + /// FindItemExtInfo finds a list of all values of the given key(s). + /// + public List? FindItemExtInfo(Protoconf.FruitType extType) => + _indexItemExtInfoMap.TryGetValue(extType, out var value) ? value : null; + + /// + /// FindFirstItemExtInfo finds the first value of the given key(s), + /// or null if no value found. + /// + public Protoconf.ItemConf.Types.Item? FindFirstItemExtInfo(Protoconf.FruitType extType) => + FindItemExtInfo(extType)?.FirstOrDefault(); + + // Index: (ID,Name)@AwardItem + + /// + /// FindAwardItemMap finds the index: key((ID,Name)@AwardItem) to value(Protoconf.ItemConf.Types.Item) map. + /// One key may correspond to multiple values, which are represented by a list. + /// + public ref readonly Index_AwardItemMap FindAwardItemMap() => ref _indexAwardItemMap; + + /// + /// FindAwardItem finds a list of all values of the given key(s). + /// + public List? FindAwardItem(uint id, string name) => + _indexAwardItemMap.TryGetValue(new Index_AwardItemKey(id, name), out var value) ? value : null; + + /// + /// FindFirstAwardItem finds the first value of the given key(s), + /// or null if no value found. + /// + public Protoconf.ItemConf.Types.Item? FindFirstAwardItem(uint id, string name) => + FindAwardItem(id, name)?.FirstOrDefault(); + + // Index: (ID,Type,Param,ExtType)@SpecialItem + + /// + /// FindSpecialItemMap finds the index: key((ID,Type,Param,ExtType)@SpecialItem) to value(Protoconf.ItemConf.Types.Item) map. + /// One key may correspond to multiple values, which are represented by a list. + /// + public ref readonly Index_SpecialItemMap FindSpecialItemMap() => ref _indexSpecialItemMap; + + /// + /// FindSpecialItem finds a list of all values of the given key(s). + /// + public List? FindSpecialItem(uint id, Protoconf.FruitType type, int param, Protoconf.FruitType extType) => + _indexSpecialItemMap.TryGetValue(new Index_SpecialItemKey(id, type, param, extType), out var value) ? value : null; + + /// + /// FindFirstSpecialItem finds the first value of the given key(s), + /// or null if no value found. + /// + public Protoconf.ItemConf.Types.Item? FindFirstSpecialItem(uint id, Protoconf.FruitType type, int param, Protoconf.FruitType extType) => + FindSpecialItem(id, type, param, extType)?.FirstOrDefault(); + + // Index: PathDir@ItemPathDir + + /// + /// FindItemPathDirMap finds the index: key(PathDir@ItemPathDir) to value(Protoconf.ItemConf.Types.Item) map. + /// One key may correspond to multiple values, which are represented by a list. + /// + public ref readonly Index_ItemPathDirMap FindItemPathDirMap() => ref _indexItemPathDirMap; + + /// + /// FindItemPathDir finds a list of all values of the given key(s). + /// + public List? FindItemPathDir(string dir) => + _indexItemPathDirMap.TryGetValue(dir, out var value) ? value : null; + + /// + /// FindFirstItemPathDir finds the first value of the given key(s), + /// or null if no value found. + /// + public Protoconf.ItemConf.Types.Item? FindFirstItemPathDir(string dir) => + FindItemPathDir(dir)?.FirstOrDefault(); + + // Index: PathName@ItemPathName + + /// + /// FindItemPathNameMap finds the index: key(PathName@ItemPathName) to value(Protoconf.ItemConf.Types.Item) map. + /// One key may correspond to multiple values, which are represented by a list. + /// + public ref readonly Index_ItemPathNameMap FindItemPathNameMap() => ref _indexItemPathNameMap; + + /// + /// FindItemPathName finds a list of all values of the given key(s). + /// + public List? FindItemPathName(string name) => + _indexItemPathNameMap.TryGetValue(name, out var value) ? value : null; + + /// + /// FindFirstItemPathName finds the first value of the given key(s), + /// or null if no value found. + /// + public Protoconf.ItemConf.Types.Item? FindFirstItemPathName(string name) => + FindItemPathName(name)?.FirstOrDefault(); + + // Index: PathFriendID@ItemPathFriendID + + /// + /// FindItemPathFriendIDMap finds the index: key(PathFriendID@ItemPathFriendID) to value(Protoconf.ItemConf.Types.Item) map. + /// One key may correspond to multiple values, which are represented by a list. + /// + public ref readonly Index_ItemPathFriendIDMap FindItemPathFriendIDMap() => ref _indexItemPathFriendIdMap; + + /// + /// FindItemPathFriendID finds a list of all values of the given key(s). + /// + public List? FindItemPathFriendID(uint id) => + _indexItemPathFriendIdMap.TryGetValue(id, out var value) ? value : null; + + /// + /// FindFirstItemPathFriendID finds the first value of the given key(s), + /// or null if no value found. + /// + public Protoconf.ItemConf.Types.Item? FindFirstItemPathFriendID(uint id) => + FindItemPathFriendID(id)?.FirstOrDefault(); + + // Index: UseEffectType@UseEffectType + + /// + /// FindUseEffectTypeMap finds the index: key(UseEffectType@UseEffectType) to value(Protoconf.ItemConf.Types.Item) map. + /// One key may correspond to multiple values, which are represented by a list. + /// + public ref readonly Index_UseEffectTypeMap FindUseEffectTypeMap() => ref _indexUseEffectTypeMap; + + /// + /// FindUseEffectType finds a list of all values of the given key(s). + /// + public List? FindUseEffectType(Protoconf.UseEffect.Types.Type type) => + _indexUseEffectTypeMap.TryGetValue(type, out var value) ? value : null; + + /// + /// FindFirstUseEffectType finds the first value of the given key(s), + /// or null if no value found. + /// + public Protoconf.ItemConf.Types.Item? FindFirstUseEffectType(Protoconf.UseEffect.Types.Type type) => + FindUseEffectType(type)?.FirstOrDefault(); + + // OrderedIndex: ExtType@ExtType + + /// + /// FindExtTypeMap finds the ordered index: key(ExtType@ExtType) to value(Protoconf.ItemConf.Types.Item) sorted map. + /// One key may correspond to multiple values, which are represented by a list. + /// + public ref readonly OrderedIndex_ExtTypeMap FindExtTypeMap() => ref _orderedIndexExtTypeMap; + + /// + /// FindExtType finds a list of all values of the given key(s). + /// + public List? FindExtType(Protoconf.FruitType extType) => + _orderedIndexExtTypeMap.TryGetValue(extType, out var value) ? value : null; + + /// + /// FindFirstExtType finds the first value of the given key(s), + /// or null if no value found. + /// + public Protoconf.ItemConf.Types.Item? FindFirstExtType(Protoconf.FruitType extType) => + FindExtType(extType)?.FirstOrDefault(); + + // OrderedIndex: (Param,ExtType)@ParamExtType + + /// + /// FindParamExtTypeMap finds the ordered index: key((Param,ExtType)@ParamExtType) to value(Protoconf.ItemConf.Types.Item) sorted map. + /// One key may correspond to multiple values, which are represented by a list. + /// + public ref readonly OrderedIndex_ParamExtTypeMap FindParamExtTypeMap() => ref _orderedIndexParamExtTypeMap; + + /// + /// FindParamExtType finds a list of all values of the given key(s). + /// + public List? FindParamExtType(int param, Protoconf.FruitType extType) => + _orderedIndexParamExtTypeMap.TryGetValue(new OrderedIndex_ParamExtTypeKey(param, extType), out var value) ? value : null; + + /// + /// FindFirstParamExtType finds the first value of the given key(s), + /// or null if no value found. + /// + public Protoconf.ItemConf.Types.Item? FindFirstParamExtType(int param, Protoconf.FruitType extType) => + FindParamExtType(param, extType)?.FirstOrDefault(); + } +} diff --git a/test/csharp-tableau-loader/tableau/Load.pc.cs b/test/csharp-tableau-loader/tableau/Load.pc.cs new file mode 100644 index 00000000..e64ca47c --- /dev/null +++ b/test/csharp-tableau-loader/tableau/Load.pc.cs @@ -0,0 +1,228 @@ +// +// Code generated by protoc-gen-csharp-tableau-loader. DO NOT EDIT. +// versions: +// - protoc-gen-csharp-tableau-loader v0.1.0 +// - protoc v3.19.3 +// +#nullable enable +using pb = global::Google.Protobuf; +using pbr = global::Google.Protobuf.Reflection; +namespace Tableau +{ + /// + /// Load provides functions for loading and parsing protobuf messages from files. + /// + public static class Load + { + /// + /// ReadFunc reads the config file and returns its content. + /// + public delegate byte[] ReadFunc(string path); + /// + /// LoadFunc defines a func which can load message's content based on the + /// given descriptor, path, format, and options. + /// + public delegate pb::IMessage? LoadFunc(pbr::MessageDescriptor desc, string dir, Format fmt, in MessagerOptions? options); + + /// + /// BaseOptions is the common options for both global-level and messager-level options. + /// + public class BaseOptions + { + /// + /// Whether to ignore unknown JSON fields during parsing. + /// + public bool? IgnoreUnknownFields { get; set; } + /// + /// You can specify custom read function to read a config file's content. + /// Default is File.ReadAllBytes. + /// + public ReadFunc? ReadFunc { get; set; } + /// + /// You can specify custom load function to load a messager's content. + /// Default is LoadMessager. + /// + public LoadFunc? LoadFunc { get; set; } + } + + /// + /// Options is the global-level options, which contains both global-level and + /// messager-level options. + /// + public class Options : BaseOptions + { + /// + /// MessagerOptions maps each messager name to a MessagerOptions. + /// If specified, then the messager will be parsed with the given options directly. + /// + public Dictionary? MessagerOptions { get; set; } + /// + /// ParseMessagerOptionsByName parses messager options with both global-level and + /// messager-level options taken into consideration. + /// + public MessagerOptions ParseMessagerOptionsByName(string name) + { + var mopts = MessagerOptions?.TryGetValue(name, out var val) == true ? (MessagerOptions)val.Clone() : new MessagerOptions(); + mopts.IgnoreUnknownFields ??= IgnoreUnknownFields; + mopts.ReadFunc ??= ReadFunc; + mopts.LoadFunc ??= LoadFunc; + return mopts; + } + } + + /// + /// MessagerOptions defines the options for loading a messager. + /// + public class MessagerOptions : BaseOptions, ICloneable + { + /// + /// Path maps each messager name to a corresponding config file path. + /// If specified, then the main messager will be parsed from the file + /// directly, other than the specified load dir. + /// + public string? Path { get; set; } + + public object Clone() + { + return new MessagerOptions + { + IgnoreUnknownFields = IgnoreUnknownFields, + ReadFunc = ReadFunc, + LoadFunc = LoadFunc, + Path = Path + }; + } + } + + /// + /// LoadMessager loads a protobuf message from the specified file path and format. + /// + public static pb::IMessage? LoadMessager(pbr::MessageDescriptor desc, string path, Format fmt, in MessagerOptions? options = null) + { + var readFunc = options?.ReadFunc ?? File.ReadAllBytes; + byte[] content; + try + { + content = readFunc(path); + } + catch (Exception ex) + { + Util.SetErrMsg($"failed to read {path}: {ex.Message}"); + throw; + } + return Unmarshal(content, desc, fmt, options); + } + + /// + /// LoadMessagerInDir loads a protobuf message from the specified directory and format. + /// It resolves the file path based on the message descriptor name. + /// + public static pb::IMessage? LoadMessagerInDir(pbr::MessageDescriptor desc, string dir, Format fmt, in MessagerOptions? options = null) + { + string name = desc.Name; + string path = ""; + if (options?.Path != null) + { + path = options.Path; + fmt = Util.GetFormat(path); + } + if (path == "") + { + string filename = name + Util.Format2Ext(fmt); + path = Path.Combine(dir, filename); + } + var loadFunc = options?.LoadFunc ?? LoadMessager; + return loadFunc(desc, path, fmt, options); + } + + /// + /// Unmarshal parses the given byte content into a protobuf message based on the specified format. + /// + public static pb::IMessage? Unmarshal(byte[] content, pbr::MessageDescriptor desc, Format fmt, in MessagerOptions? options = null) + { + switch (fmt) + { + case Format.JSON: + try + { + var parser = new pb::JsonParser( + pb::JsonParser.Settings.Default.WithIgnoreUnknownFields(options?.IgnoreUnknownFields ?? false) + ); + return parser.Parse(new StreamReader(new MemoryStream(content)), desc); + } + catch (Exception ex) + { + Util.SetErrMsg($"failed to parse {desc.Name}.json: {ex.Message}"); + throw; + } + case Format.Bin: + try + { + return desc.Parser.ParseFrom(content); + } + catch (Exception ex) + { + Util.SetErrMsg($"failed to parse {desc.Name}.binpb: {ex.Message}"); + throw; + } + default: + Util.SetErrMsg($"unknown format: {fmt}"); + return null; + } + } + } + + /// + /// IMessagerName is an interface that provides the static Name() method for messagers. + /// + public interface IMessagerName + { + static abstract string Name(); + } + + /// + /// Messager is the base class for all generated configuration messagers. + /// It is designed for three goals: + /// 1. Easy use: simple yet powerful accessors. + /// 2. Elegant API: concise and clean functions. + /// 3. Extensibility: Map, OrderedMap, Index, OrderedIndex... + /// + public abstract class Messager + { + /// + /// Stats contains statistics info about loading. + /// + public class Stats + { + /// Total load time consuming. + public TimeSpan Duration; + } + + protected Stats LoadStats = new(); + + /// + /// GetStats returns the loading stats info. + /// + public ref readonly Stats GetStats() => ref LoadStats; + + /// + /// Load fills message from file in the specified directory and format. + /// + public abstract bool Load(string dir, Format fmt, in Load.MessagerOptions? options = null); + + /// + /// Message returns the inner protobuf message data. + /// + public virtual pb::IMessage? Message() => null; + + /// + /// ProcessAfterLoad is invoked after this messager loaded. + /// + protected virtual bool ProcessAfterLoad() => true; + + /// + /// ProcessAfterLoadAll is invoked after all messagers loaded. + /// + public virtual bool ProcessAfterLoadAll(in Hub hub) => true; + } +} diff --git a/test/csharp-tableau-loader/tableau/PatchConf.pc.cs b/test/csharp-tableau-loader/tableau/PatchConf.pc.cs new file mode 100644 index 00000000..59383a89 --- /dev/null +++ b/test/csharp-tableau-loader/tableau/PatchConf.pc.cs @@ -0,0 +1,190 @@ +// +// Code generated by protoc-gen-csharp-tableau-loader. DO NOT EDIT. +// versions: +// - protoc-gen-csharp-tableau-loader v0.1.0 +// - protoc v3.19.3 +// source: patch_conf.proto +// +#nullable enable +using pb = global::Google.Protobuf; +namespace Tableau +{ + /// + /// PatchReplaceConf is a wrapper around protobuf message Protoconf.PatchReplaceConf. + /// + public class PatchReplaceConf : Messager, IMessagerName + { + private Protoconf.PatchReplaceConf _data = new(); + + /// + /// Name returns the PatchReplaceConf's message name. + /// + public static string Name() => Protoconf.PatchReplaceConf.Descriptor.Name; + + /// + /// Load loads PatchReplaceConf's content in the given dir, based on format and messager options. + /// + public override bool Load(string dir, Format fmt, in Load.MessagerOptions? options = null) + { + var start = DateTime.Now; + try + { + _data = (Protoconf.PatchReplaceConf)( + Tableau.Load.LoadMessagerInDir(Protoconf.PatchReplaceConf.Descriptor, dir, fmt, options) + ?? throw new InvalidOperationException() + ); + } + catch (Exception ex) + { + if (string.IsNullOrEmpty(Util.GetErrMsg())) + { + Util.SetErrMsg($"failed to load PatchReplaceConf: {ex.Message}"); + } + return false; + } + LoadStats.Duration = DateTime.Now - start; + return ProcessAfterLoad(); + } + + /// + /// Data returns the PatchReplaceConf's inner message data. + /// + public ref readonly Protoconf.PatchReplaceConf Data() => ref _data; + + /// + /// Message returns the PatchReplaceConf's inner message data. + /// + public override pb::IMessage? Message() => _data; + } + + /// + /// PatchMergeConf is a wrapper around protobuf message Protoconf.PatchMergeConf. + /// + public class PatchMergeConf : Messager, IMessagerName + { + private Protoconf.PatchMergeConf _data = new(); + + /// + /// Name returns the PatchMergeConf's message name. + /// + public static string Name() => Protoconf.PatchMergeConf.Descriptor.Name; + + /// + /// Load loads PatchMergeConf's content in the given dir, based on format and messager options. + /// + public override bool Load(string dir, Format fmt, in Load.MessagerOptions? options = null) + { + var start = DateTime.Now; + try + { + _data = (Protoconf.PatchMergeConf)( + Tableau.Load.LoadMessagerInDir(Protoconf.PatchMergeConf.Descriptor, dir, fmt, options) + ?? throw new InvalidOperationException() + ); + } + catch (Exception ex) + { + if (string.IsNullOrEmpty(Util.GetErrMsg())) + { + Util.SetErrMsg($"failed to load PatchMergeConf: {ex.Message}"); + } + return false; + } + LoadStats.Duration = DateTime.Now - start; + return ProcessAfterLoad(); + } + + /// + /// Data returns the PatchMergeConf's inner message data. + /// + public ref readonly Protoconf.PatchMergeConf Data() => ref _data; + + /// + /// Message returns the PatchMergeConf's inner message data. + /// + public override pb::IMessage? Message() => _data; + + /// + /// Get1 finds value in the 1st-level map. + /// It will return null if the key is not found. + /// + public Protoconf.Item? Get1(uint id) => + _data.ItemMap?.TryGetValue(id, out var val) == true ? val : null; + } + + /// + /// RecursivePatchConf is a wrapper around protobuf message Protoconf.RecursivePatchConf. + /// + public class RecursivePatchConf : Messager, IMessagerName + { + private Protoconf.RecursivePatchConf _data = new(); + + /// + /// Name returns the RecursivePatchConf's message name. + /// + public static string Name() => Protoconf.RecursivePatchConf.Descriptor.Name; + + /// + /// Load loads RecursivePatchConf's content in the given dir, based on format and messager options. + /// + public override bool Load(string dir, Format fmt, in Load.MessagerOptions? options = null) + { + var start = DateTime.Now; + try + { + _data = (Protoconf.RecursivePatchConf)( + Tableau.Load.LoadMessagerInDir(Protoconf.RecursivePatchConf.Descriptor, dir, fmt, options) + ?? throw new InvalidOperationException() + ); + } + catch (Exception ex) + { + if (string.IsNullOrEmpty(Util.GetErrMsg())) + { + Util.SetErrMsg($"failed to load RecursivePatchConf: {ex.Message}"); + } + return false; + } + LoadStats.Duration = DateTime.Now - start; + return ProcessAfterLoad(); + } + + /// + /// Data returns the RecursivePatchConf's inner message data. + /// + public ref readonly Protoconf.RecursivePatchConf Data() => ref _data; + + /// + /// Message returns the RecursivePatchConf's inner message data. + /// + public override pb::IMessage? Message() => _data; + + /// + /// Get1 finds value in the 1st-level map. + /// It will return null if the key is not found. + /// + public Protoconf.RecursivePatchConf.Types.Shop? Get1(uint shopId) => + _data.ShopMap?.TryGetValue(shopId, out var val) == true ? val : null; + + /// + /// Get2 finds value in the 2nd-level map. + /// It will return null if the key is not found. + /// + public Protoconf.RecursivePatchConf.Types.Shop.Types.Goods? Get2(uint shopId, uint goodsId) => + Get1(shopId)?.GoodsMap?.TryGetValue(goodsId, out var val) == true ? val : null; + + /// + /// Get3 finds value in the 3rd-level map. + /// It will return null if the key is not found. + /// + public Protoconf.RecursivePatchConf.Types.Shop.Types.Goods.Types.Currency? Get3(uint shopId, uint goodsId, uint type) => + Get2(shopId, goodsId)?.CurrencyMap?.TryGetValue(type, out var val) == true ? val : null; + + /// + /// Get4 finds value in the 4th-level map. + /// It will return null if the key is not found. + /// + public int? Get4(uint shopId, uint goodsId, uint type, int key4) => + Get3(shopId, goodsId, type)?.ValueList?.TryGetValue(key4, out var val) == true ? val : null; + } +} diff --git a/test/csharp-tableau-loader/tableau/TestConf.pc.cs b/test/csharp-tableau-loader/tableau/TestConf.pc.cs new file mode 100644 index 00000000..413d6cd7 --- /dev/null +++ b/test/csharp-tableau-loader/tableau/TestConf.pc.cs @@ -0,0 +1,889 @@ +// +// Code generated by protoc-gen-csharp-tableau-loader. DO NOT EDIT. +// versions: +// - protoc-gen-csharp-tableau-loader v0.1.0 +// - protoc v3.19.3 +// source: test_conf.proto +// +#nullable enable +using pb = global::Google.Protobuf; +namespace Tableau +{ + /// + /// ActivityConf is a wrapper around protobuf message Protoconf.ActivityConf. + /// + public class ActivityConf : Messager, IMessagerName + { + // OrderedMap types. + public class OrderedMap_int32Map : SortedDictionary; + + public class OrderedMap_protoconf_SectionValue(OrderedMap_int32Map item1, Protoconf.Section item2) + : Tuple(item1, item2); + public class OrderedMap_protoconf_SectionMap : SortedDictionary; + + public class OrderedMap_Activity_ChapterValue(OrderedMap_protoconf_SectionMap item1, Protoconf.ActivityConf.Types.Activity.Types.Chapter item2) + : Tuple(item1, item2); + public class OrderedMap_Activity_ChapterMap : SortedDictionary; + + public class OrderedMap_ActivityValue(OrderedMap_Activity_ChapterMap item1, Protoconf.ActivityConf.Types.Activity item2) + : Tuple(item1, item2); + public class OrderedMap_ActivityMap : SortedDictionary; + + private OrderedMap_ActivityMap _orderedMap = []; + + + // LevelIndex keys. + public readonly struct LevelIndex_Activity_ChapterKey : IEquatable + { + public ulong ActivityId { get; } + public uint ChapterId { get; } + + public LevelIndex_Activity_ChapterKey(ulong activityId, uint chapterId) + { + ActivityId = activityId; + ChapterId = chapterId; + } + + public bool Equals(LevelIndex_Activity_ChapterKey other) => + (ActivityId, ChapterId).Equals((other.ActivityId, other.ChapterId)); + + public override int GetHashCode() => + (ActivityId, ChapterId).GetHashCode(); + } + + // Index types. + // Index: ActivityName + public class Index_ActivityMap : Dictionary>; + + private Index_ActivityMap _indexActivityMap = []; + + // Index: ChapterID + public class Index_ChapterMap : Dictionary>; + + private Index_ChapterMap _indexChapterMap = []; + + private Dictionary _indexChapterMap1 = []; + + // Index: ChapterName@NamedChapter + public class Index_NamedChapterMap : Dictionary>; + + private Index_NamedChapterMap _indexNamedChapterMap = []; + + private Dictionary _indexNamedChapterMap1 = []; + + // Index: SectionItemID@Award + public class Index_AwardMap : Dictionary>; + + private Index_AwardMap _indexAwardMap = []; + + private Dictionary _indexAwardMap1 = []; + + private Dictionary _indexAwardMap2 = []; + + private Protoconf.ActivityConf _data = new(); + + /// + /// Name returns the ActivityConf's message name. + /// + public static string Name() => Protoconf.ActivityConf.Descriptor.Name; + + /// + /// Load loads ActivityConf's content in the given dir, based on format and messager options. + /// + public override bool Load(string dir, Format fmt, in Load.MessagerOptions? options = null) + { + var start = DateTime.Now; + try + { + _data = (Protoconf.ActivityConf)( + Tableau.Load.LoadMessagerInDir(Protoconf.ActivityConf.Descriptor, dir, fmt, options) + ?? throw new InvalidOperationException() + ); + } + catch (Exception ex) + { + if (string.IsNullOrEmpty(Util.GetErrMsg())) + { + Util.SetErrMsg($"failed to load ActivityConf: {ex.Message}"); + } + return false; + } + LoadStats.Duration = DateTime.Now - start; + return ProcessAfterLoad(); + } + + /// + /// Data returns the ActivityConf's inner message data. + /// + public ref readonly Protoconf.ActivityConf Data() => ref _data; + + /// + /// Message returns the ActivityConf's inner message data. + /// + public override pb::IMessage? Message() => _data; + + /// + /// ProcessAfterLoad runs after this messager is loaded. + /// + protected override bool ProcessAfterLoad() + { + // OrderedMap init. + _orderedMap.Clear(); + foreach (var (key1, value1) in _data.ActivityMap) + { + var ordered_map1 = new OrderedMap_Activity_ChapterMap(); + foreach (var (key2, value2) in value1.ChapterMap) + { + var ordered_map2 = new OrderedMap_protoconf_SectionMap(); + foreach (var (key3, value3) in value2.SectionMap) + { + var ordered_map3 = new OrderedMap_int32Map(); + foreach (var (key4, value4) in value3.SectionRankMap) + { + ordered_map3[key4] = value4; + } + ordered_map2[key3] = new OrderedMap_protoconf_SectionValue(ordered_map3, value3); + } + ordered_map1[key2] = new OrderedMap_Activity_ChapterValue(ordered_map2, value2); + } + _orderedMap[key1] = new OrderedMap_ActivityValue(ordered_map1, value1); + } + // Index init. + _indexActivityMap.Clear(); + _indexChapterMap.Clear(); + _indexChapterMap1.Clear(); + _indexNamedChapterMap.Clear(); + _indexNamedChapterMap1.Clear(); + _indexAwardMap.Clear(); + _indexAwardMap1.Clear(); + _indexAwardMap2.Clear(); + foreach (var item1 in _data.ActivityMap) + { + var k1 = item1.Key; + { + // Index: ActivityName + var key = item1.Value.ActivityName; + { + var list = _indexActivityMap.TryGetValue(key, out var existingList) ? + existingList : _indexActivityMap[key] = []; + list.Add(item1.Value); + } + } + foreach (var item2 in item1.Value.ChapterMap) + { + var k2 = item2.Key; + { + // Index: ChapterID + var key = item2.Value.ChapterId; + { + var list = _indexChapterMap.TryGetValue(key, out var existingList) ? + existingList : _indexChapterMap[key] = []; + list.Add(item2.Value); + } + { + var map = _indexChapterMap1.TryGetValue(k1, out var existingMap) ? + existingMap : _indexChapterMap1[k1] = []; + var list = map.TryGetValue(key, out var existingList) ? + existingList : map[key] = []; + list.Add(item2.Value); + } + } + { + // Index: ChapterName@NamedChapter + var key = item2.Value.ChapterName; + { + var list = _indexNamedChapterMap.TryGetValue(key, out var existingList) ? + existingList : _indexNamedChapterMap[key] = []; + list.Add(item2.Value); + } + { + var map = _indexNamedChapterMap1.TryGetValue(k1, out var existingMap) ? + existingMap : _indexNamedChapterMap1[k1] = []; + var list = map.TryGetValue(key, out var existingList) ? + existingList : map[key] = []; + list.Add(item2.Value); + } + } + foreach (var item3 in item2.Value.SectionMap) + { + foreach (var item4 in item3.Value.SectionItemList) + { + { + // Index: SectionItemID@Award + var key = item4.Id; + { + var list = _indexAwardMap.TryGetValue(key, out var existingList) ? + existingList : _indexAwardMap[key] = []; + list.Add(item4); + } + { + var map = _indexAwardMap1.TryGetValue(k1, out var existingMap) ? + existingMap : _indexAwardMap1[k1] = []; + var list = map.TryGetValue(key, out var existingList) ? + existingList : map[key] = []; + list.Add(item4); + } + { + var mapKey = new LevelIndex_Activity_ChapterKey(k1, k2); + var map = _indexAwardMap2.TryGetValue(mapKey, out var existingMap) ? + existingMap : _indexAwardMap2[mapKey] = []; + var list = map.TryGetValue(key, out var existingList) ? + existingList : map[key] = []; + list.Add(item4); + } + } + } + } + } + } + // Index(sort): ChapterName@NamedChapter + Comparison indexNamedChapterMapComparison = (a, b) => + (a.AwardId).CompareTo((b.AwardId)); + foreach (var itemList in _indexNamedChapterMap.Values) + { + itemList.Sort(indexNamedChapterMapComparison); + } + foreach (var itemDict in _indexNamedChapterMap1.Values) + { + foreach (var itemList in itemDict.Values) + { + itemList.Sort(indexNamedChapterMapComparison); + } + } + return true; + } + + /// + /// Get1 finds value in the 1st-level map. + /// It will return null if the key is not found. + /// + public Protoconf.ActivityConf.Types.Activity? Get1(ulong activityId) => + _data.ActivityMap?.TryGetValue(activityId, out var val) == true ? val : null; + + /// + /// Get2 finds value in the 2nd-level map. + /// It will return null if the key is not found. + /// + public Protoconf.ActivityConf.Types.Activity.Types.Chapter? Get2(ulong activityId, uint chapterId) => + Get1(activityId)?.ChapterMap?.TryGetValue(chapterId, out var val) == true ? val : null; + + /// + /// Get3 finds value in the 3rd-level map. + /// It will return null if the key is not found. + /// + public Protoconf.Section? Get3(ulong activityId, uint chapterId, uint sectionId) => + Get2(activityId, chapterId)?.SectionMap?.TryGetValue(sectionId, out var val) == true ? val : null; + + /// + /// Get4 finds value in the 4th-level map. + /// It will return null if the key is not found. + /// + public int? Get4(ulong activityId, uint chapterId, uint sectionId, uint key4) => + Get3(activityId, chapterId, sectionId)?.SectionRankMap?.TryGetValue(key4, out var val) == true ? val : null; + + // OrderedMap accessers. + /// + /// GetOrderedMap returns the 1st-level ordered map. + /// + public ref readonly OrderedMap_ActivityMap GetOrderedMap() => ref _orderedMap; + + /// + /// GetOrderedMap1 finds value in the 1st-level ordered map. + /// It will return null if the key is not found. + /// + public OrderedMap_Activity_ChapterMap? GetOrderedMap1(ulong activityId) => + _orderedMap.TryGetValue(activityId, out var value) ? value.Item1 : null; + + /// + /// GetOrderedMap2 finds value in the 2nd-level ordered map. + /// It will return null if the key is not found. + /// + public OrderedMap_protoconf_SectionMap? GetOrderedMap2(ulong activityId, uint chapterId) => + GetOrderedMap1(activityId)?.TryGetValue(chapterId, out var value) == true ? value.Item1 : null; + + /// + /// GetOrderedMap3 finds value in the 3rd-level ordered map. + /// It will return null if the key is not found. + /// + public OrderedMap_int32Map? GetOrderedMap3(ulong activityId, uint chapterId, uint sectionId) => + GetOrderedMap2(activityId, chapterId)?.TryGetValue(sectionId, out var value) == true ? value.Item1 : null; + + // Index: ActivityName + + /// + /// FindActivityMap finds the index: key(ActivityName) to value(Protoconf.ActivityConf.Types.Activity) map. + /// One key may correspond to multiple values, which are represented by a list. + /// + public ref readonly Index_ActivityMap FindActivityMap() => ref _indexActivityMap; + + /// + /// FindActivity finds a list of all values of the given key(s). + /// + public List? FindActivity(string activityName) => + _indexActivityMap.TryGetValue(activityName, out var value) ? value : null; + + /// + /// FindFirstActivity finds the first value of the given key(s), + /// or null if no value found. + /// + public Protoconf.ActivityConf.Types.Activity? FindFirstActivity(string activityName) => + FindActivity(activityName)?.FirstOrDefault(); + + // Index: ChapterID + + /// + /// FindChapterMap finds the index: key(ChapterID) to value(Protoconf.ActivityConf.Types.Activity.Types.Chapter) map. + /// One key may correspond to multiple values, which are represented by a list. + /// + public ref readonly Index_ChapterMap FindChapterMap() => ref _indexChapterMap; + + /// + /// FindChapter finds a list of all values of the given key(s). + /// + public List? FindChapter(uint chapterId) => + _indexChapterMap.TryGetValue(chapterId, out var value) ? value : null; + + /// + /// FindFirstChapter finds the first value of the given key(s), + /// or null if no value found. + /// + public Protoconf.ActivityConf.Types.Activity.Types.Chapter? FindFirstChapter(uint chapterId) => + FindChapter(chapterId)?.FirstOrDefault(); + + /// + /// FindChapterMap1 finds the index: key(ChapterID) to value(Protoconf.ActivityConf.Types.Activity.Types.Chapter), + /// which is the upper 1st-level map specified by (activityId). + /// One key may correspond to multiple values, which are represented by a list. + /// + public Index_ChapterMap? FindChapterMap1(ulong activityId) => + _indexChapterMap1.TryGetValue(activityId, out var value) ? value : null; + + /// + /// FindChapter1 finds a list of all values of the given key(s) in the upper 1st-level map + /// specified by (activityId). + /// + public List? FindChapter1(ulong activityId, uint chapterId) => + FindChapterMap1(activityId)?.TryGetValue(chapterId, out var value) == true ? value : null; + + /// + /// FindFirstChapter1 finds the first value of the given key(s) in the upper 1st-level map + /// specified by (activityId), or null if no value found. + /// + public Protoconf.ActivityConf.Types.Activity.Types.Chapter? FindFirstChapter1(ulong activityId, uint chapterId) => + FindChapter1(activityId, chapterId)?.FirstOrDefault(); + + // Index: ChapterName@NamedChapter + + /// + /// FindNamedChapterMap finds the index: key(ChapterName@NamedChapter) to value(Protoconf.ActivityConf.Types.Activity.Types.Chapter) map. + /// One key may correspond to multiple values, which are represented by a list. + /// + public ref readonly Index_NamedChapterMap FindNamedChapterMap() => ref _indexNamedChapterMap; + + /// + /// FindNamedChapter finds a list of all values of the given key(s). + /// + public List? FindNamedChapter(string chapterName) => + _indexNamedChapterMap.TryGetValue(chapterName, out var value) ? value : null; + + /// + /// FindFirstNamedChapter finds the first value of the given key(s), + /// or null if no value found. + /// + public Protoconf.ActivityConf.Types.Activity.Types.Chapter? FindFirstNamedChapter(string chapterName) => + FindNamedChapter(chapterName)?.FirstOrDefault(); + + /// + /// FindNamedChapterMap1 finds the index: key(ChapterName@NamedChapter) to value(Protoconf.ActivityConf.Types.Activity.Types.Chapter), + /// which is the upper 1st-level map specified by (activityId). + /// One key may correspond to multiple values, which are represented by a list. + /// + public Index_NamedChapterMap? FindNamedChapterMap1(ulong activityId) => + _indexNamedChapterMap1.TryGetValue(activityId, out var value) ? value : null; + + /// + /// FindNamedChapter1 finds a list of all values of the given key(s) in the upper 1st-level map + /// specified by (activityId). + /// + public List? FindNamedChapter1(ulong activityId, string chapterName) => + FindNamedChapterMap1(activityId)?.TryGetValue(chapterName, out var value) == true ? value : null; + + /// + /// FindFirstNamedChapter1 finds the first value of the given key(s) in the upper 1st-level map + /// specified by (activityId), or null if no value found. + /// + public Protoconf.ActivityConf.Types.Activity.Types.Chapter? FindFirstNamedChapter1(ulong activityId, string chapterName) => + FindNamedChapter1(activityId, chapterName)?.FirstOrDefault(); + + // Index: SectionItemID@Award + + /// + /// FindAwardMap finds the index: key(SectionItemID@Award) to value(Protoconf.Section.Types.SectionItem) map. + /// One key may correspond to multiple values, which are represented by a list. + /// + public ref readonly Index_AwardMap FindAwardMap() => ref _indexAwardMap; + + /// + /// FindAward finds a list of all values of the given key(s). + /// + public List? FindAward(uint id) => + _indexAwardMap.TryGetValue(id, out var value) ? value : null; + + /// + /// FindFirstAward finds the first value of the given key(s), + /// or null if no value found. + /// + public Protoconf.Section.Types.SectionItem? FindFirstAward(uint id) => + FindAward(id)?.FirstOrDefault(); + + /// + /// FindAwardMap1 finds the index: key(SectionItemID@Award) to value(Protoconf.Section.Types.SectionItem), + /// which is the upper 1st-level map specified by (activityId). + /// One key may correspond to multiple values, which are represented by a list. + /// + public Index_AwardMap? FindAwardMap1(ulong activityId) => + _indexAwardMap1.TryGetValue(activityId, out var value) ? value : null; + + /// + /// FindAward1 finds a list of all values of the given key(s) in the upper 1st-level map + /// specified by (activityId). + /// + public List? FindAward1(ulong activityId, uint id) => + FindAwardMap1(activityId)?.TryGetValue(id, out var value) == true ? value : null; + + /// + /// FindFirstAward1 finds the first value of the given key(s) in the upper 1st-level map + /// specified by (activityId), or null if no value found. + /// + public Protoconf.Section.Types.SectionItem? FindFirstAward1(ulong activityId, uint id) => + FindAward1(activityId, id)?.FirstOrDefault(); + + /// + /// FindAwardMap2 finds the index: key(SectionItemID@Award) to value(Protoconf.Section.Types.SectionItem), + /// which is the upper 2nd-level map specified by (activityId, chapterId). + /// One key may correspond to multiple values, which are represented by a list. + /// + public Index_AwardMap? FindAwardMap2(ulong activityId, uint chapterId) => + _indexAwardMap2.TryGetValue(new LevelIndex_Activity_ChapterKey(activityId, chapterId), out var value) ? value : null; + + /// + /// FindAward2 finds a list of all values of the given key(s) in the upper 2nd-level map + /// specified by (activityId, chapterId). + /// + public List? FindAward2(ulong activityId, uint chapterId, uint id) => + FindAwardMap2(activityId, chapterId)?.TryGetValue(id, out var value) == true ? value : null; + + /// + /// FindFirstAward2 finds the first value of the given key(s) in the upper 2nd-level map + /// specified by (activityId, chapterId), or null if no value found. + /// + public Protoconf.Section.Types.SectionItem? FindFirstAward2(ulong activityId, uint chapterId, uint id) => + FindAward2(activityId, chapterId, id)?.FirstOrDefault(); + } + + /// + /// ChapterConf is a wrapper around protobuf message Protoconf.ChapterConf. + /// + public class ChapterConf : Messager, IMessagerName + { + private Protoconf.ChapterConf _data = new(); + + /// + /// Name returns the ChapterConf's message name. + /// + public static string Name() => Protoconf.ChapterConf.Descriptor.Name; + + /// + /// Load loads ChapterConf's content in the given dir, based on format and messager options. + /// + public override bool Load(string dir, Format fmt, in Load.MessagerOptions? options = null) + { + var start = DateTime.Now; + try + { + _data = (Protoconf.ChapterConf)( + Tableau.Load.LoadMessagerInDir(Protoconf.ChapterConf.Descriptor, dir, fmt, options) + ?? throw new InvalidOperationException() + ); + } + catch (Exception ex) + { + if (string.IsNullOrEmpty(Util.GetErrMsg())) + { + Util.SetErrMsg($"failed to load ChapterConf: {ex.Message}"); + } + return false; + } + LoadStats.Duration = DateTime.Now - start; + return ProcessAfterLoad(); + } + + /// + /// Data returns the ChapterConf's inner message data. + /// + public ref readonly Protoconf.ChapterConf Data() => ref _data; + + /// + /// Message returns the ChapterConf's inner message data. + /// + public override pb::IMessage? Message() => _data; + + /// + /// Get1 finds value in the 1st-level map. + /// It will return null if the key is not found. + /// + public Protoconf.ChapterConf.Types.Chapter? Get1(ulong id) => + _data.ChapterMap?.TryGetValue(id, out var val) == true ? val : null; + } + + /// + /// ThemeConf is a wrapper around protobuf message Protoconf.ThemeConf. + /// + public class ThemeConf : Messager, IMessagerName + { + private Protoconf.ThemeConf _data = new(); + + /// + /// Name returns the ThemeConf's message name. + /// + public static string Name() => Protoconf.ThemeConf.Descriptor.Name; + + /// + /// Load loads ThemeConf's content in the given dir, based on format and messager options. + /// + public override bool Load(string dir, Format fmt, in Load.MessagerOptions? options = null) + { + var start = DateTime.Now; + try + { + _data = (Protoconf.ThemeConf)( + Tableau.Load.LoadMessagerInDir(Protoconf.ThemeConf.Descriptor, dir, fmt, options) + ?? throw new InvalidOperationException() + ); + } + catch (Exception ex) + { + if (string.IsNullOrEmpty(Util.GetErrMsg())) + { + Util.SetErrMsg($"failed to load ThemeConf: {ex.Message}"); + } + return false; + } + LoadStats.Duration = DateTime.Now - start; + return ProcessAfterLoad(); + } + + /// + /// Data returns the ThemeConf's inner message data. + /// + public ref readonly Protoconf.ThemeConf Data() => ref _data; + + /// + /// Message returns the ThemeConf's inner message data. + /// + public override pb::IMessage? Message() => _data; + + /// + /// Get1 finds value in the 1st-level map. + /// It will return null if the key is not found. + /// + public Protoconf.ThemeConf.Types.Theme? Get1(string name) => + _data.ThemeMap?.TryGetValue(name, out var val) == true ? val : null; + + /// + /// Get2 finds value in the 2nd-level map. + /// It will return null if the key is not found. + /// + public string? Get2(string name, string param) => + Get1(name)?.ParamMap?.TryGetValue(param, out var val) == true ? val : null; + } + + /// + /// TaskConf is a wrapper around protobuf message Protoconf.TaskConf. + /// + public class TaskConf : Messager, IMessagerName + { + // Index types. + // Index: ActivityID + public class Index_TaskMap : Dictionary>; + + private Index_TaskMap _indexTaskMap = []; + + // OrderedIndex types. + // OrderedIndex: Goal@OrderedTask + public class OrderedIndex_OrderedTaskMap : SortedDictionary>; + + private OrderedIndex_OrderedTaskMap _orderedIndexOrderedTaskMap = []; + + // OrderedIndex: Expiry@TaskExpiry + public class OrderedIndex_TaskExpiryMap : SortedDictionary>; + + private OrderedIndex_TaskExpiryMap _orderedIndexTaskExpiryMap = []; + + // OrderedIndex: Expiry@SortedTaskExpiry + public class OrderedIndex_SortedTaskExpiryMap : SortedDictionary>; + + private OrderedIndex_SortedTaskExpiryMap _orderedIndexSortedTaskExpiryMap = []; + + // OrderedIndex: (Expiry,ActivityID)@ActivityExpiry + public readonly struct OrderedIndex_ActivityExpiryKey : IComparable + { + public long Expiry { get; } + public long ActivityId { get; } + + public OrderedIndex_ActivityExpiryKey(long expiry, long activityId) + { + Expiry = expiry; + ActivityId = activityId; + } + + public int CompareTo(OrderedIndex_ActivityExpiryKey other) => + (Expiry, ActivityId).CompareTo((other.Expiry, other.ActivityId)); + } + + public class OrderedIndex_ActivityExpiryMap : SortedDictionary>; + + private OrderedIndex_ActivityExpiryMap _orderedIndexActivityExpiryMap = []; + + private Protoconf.TaskConf _data = new(); + + /// + /// Name returns the TaskConf's message name. + /// + public static string Name() => Protoconf.TaskConf.Descriptor.Name; + + /// + /// Load loads TaskConf's content in the given dir, based on format and messager options. + /// + public override bool Load(string dir, Format fmt, in Load.MessagerOptions? options = null) + { + var start = DateTime.Now; + try + { + _data = (Protoconf.TaskConf)( + Tableau.Load.LoadMessagerInDir(Protoconf.TaskConf.Descriptor, dir, fmt, options) + ?? throw new InvalidOperationException() + ); + } + catch (Exception ex) + { + if (string.IsNullOrEmpty(Util.GetErrMsg())) + { + Util.SetErrMsg($"failed to load TaskConf: {ex.Message}"); + } + return false; + } + LoadStats.Duration = DateTime.Now - start; + return ProcessAfterLoad(); + } + + /// + /// Data returns the TaskConf's inner message data. + /// + public ref readonly Protoconf.TaskConf Data() => ref _data; + + /// + /// Message returns the TaskConf's inner message data. + /// + public override pb::IMessage? Message() => _data; + + /// + /// ProcessAfterLoad runs after this messager is loaded. + /// + protected override bool ProcessAfterLoad() + { + // Index init. + _indexTaskMap.Clear(); + foreach (var item1 in _data.TaskMap) + { + { + // Index: ActivityID + var key = item1.Value.ActivityId; + { + var list = _indexTaskMap.TryGetValue(key, out var existingList) ? + existingList : _indexTaskMap[key] = []; + list.Add(item1.Value); + } + } + } + // Index(sort): ActivityID + Comparison indexTaskMapComparison = (a, b) => + (a.Goal, a.Id).CompareTo((b.Goal, b.Id)); + foreach (var itemList in _indexTaskMap.Values) + { + itemList.Sort(indexTaskMapComparison); + } + // OrderedIndex init. + _orderedIndexOrderedTaskMap.Clear(); + _orderedIndexTaskExpiryMap.Clear(); + _orderedIndexSortedTaskExpiryMap.Clear(); + _orderedIndexActivityExpiryMap.Clear(); + foreach (var item1 in _data.TaskMap) + { + { + // OrderedIndex: Goal@OrderedTask + var key = item1.Value.Goal; + { + var list = _orderedIndexOrderedTaskMap.TryGetValue(key, out var existingList) ? + existingList : _orderedIndexOrderedTaskMap[key] = []; + list.Add(item1.Value); + } + } + { + // OrderedIndex: Expiry@TaskExpiry + var key = item1.Value.Expiry?.Seconds ?? 0; + { + var list = _orderedIndexTaskExpiryMap.TryGetValue(key, out var existingList) ? + existingList : _orderedIndexTaskExpiryMap[key] = []; + list.Add(item1.Value); + } + } + { + // OrderedIndex: Expiry@SortedTaskExpiry + var key = item1.Value.Expiry?.Seconds ?? 0; + { + var list = _orderedIndexSortedTaskExpiryMap.TryGetValue(key, out var existingList) ? + existingList : _orderedIndexSortedTaskExpiryMap[key] = []; + list.Add(item1.Value); + } + } + { + // OrderedIndex: (Expiry,ActivityID)@ActivityExpiry + var key = new OrderedIndex_ActivityExpiryKey(item1.Value.Expiry?.Seconds ?? 0, item1.Value.ActivityId); + { + var list = _orderedIndexActivityExpiryMap.TryGetValue(key, out var existingList) ? + existingList : _orderedIndexActivityExpiryMap[key] = []; + list.Add(item1.Value); + } + } + } + // OrderedIndex(sort): Goal@OrderedTask + Comparison orderedIndexOrderedTaskMapComparison = (a, b) => + (a.Id).CompareTo((b.Id)); + foreach (var itemList in _orderedIndexOrderedTaskMap.Values) + { + itemList.Sort(orderedIndexOrderedTaskMapComparison); + } + // OrderedIndex(sort): Expiry@SortedTaskExpiry + Comparison orderedIndexSortedTaskExpiryMapComparison = (a, b) => + (a.Goal, a.Id).CompareTo((b.Goal, b.Id)); + foreach (var itemList in _orderedIndexSortedTaskExpiryMap.Values) + { + itemList.Sort(orderedIndexSortedTaskExpiryMapComparison); + } + return true; + } + + /// + /// Get1 finds value in the 1st-level map. + /// It will return null if the key is not found. + /// + public Protoconf.TaskConf.Types.Task? Get1(long id) => + _data.TaskMap?.TryGetValue(id, out var val) == true ? val : null; + + // Index: ActivityID + + /// + /// FindTaskMap finds the index: key(ActivityID) to value(Protoconf.TaskConf.Types.Task) map. + /// One key may correspond to multiple values, which are represented by a list. + /// + public ref readonly Index_TaskMap FindTaskMap() => ref _indexTaskMap; + + /// + /// FindTask finds a list of all values of the given key(s). + /// + public List? FindTask(long activityId) => + _indexTaskMap.TryGetValue(activityId, out var value) ? value : null; + + /// + /// FindFirstTask finds the first value of the given key(s), + /// or null if no value found. + /// + public Protoconf.TaskConf.Types.Task? FindFirstTask(long activityId) => + FindTask(activityId)?.FirstOrDefault(); + + // OrderedIndex: Goal@OrderedTask + + /// + /// FindOrderedTaskMap finds the ordered index: key(Goal@OrderedTask) to value(Protoconf.TaskConf.Types.Task) sorted map. + /// One key may correspond to multiple values, which are represented by a list. + /// + public ref readonly OrderedIndex_OrderedTaskMap FindOrderedTaskMap() => ref _orderedIndexOrderedTaskMap; + + /// + /// FindOrderedTask finds a list of all values of the given key(s). + /// + public List? FindOrderedTask(long goal) => + _orderedIndexOrderedTaskMap.TryGetValue(goal, out var value) ? value : null; + + /// + /// FindFirstOrderedTask finds the first value of the given key(s), + /// or null if no value found. + /// + public Protoconf.TaskConf.Types.Task? FindFirstOrderedTask(long goal) => + FindOrderedTask(goal)?.FirstOrDefault(); + + // OrderedIndex: Expiry@TaskExpiry + + /// + /// FindTaskExpiryMap finds the ordered index: key(Expiry@TaskExpiry) to value(Protoconf.TaskConf.Types.Task) sorted map. + /// One key may correspond to multiple values, which are represented by a list. + /// + public ref readonly OrderedIndex_TaskExpiryMap FindTaskExpiryMap() => ref _orderedIndexTaskExpiryMap; + + /// + /// FindTaskExpiry finds a list of all values of the given key(s). + /// + public List? FindTaskExpiry(long expiry) => + _orderedIndexTaskExpiryMap.TryGetValue(expiry, out var value) ? value : null; + + /// + /// FindFirstTaskExpiry finds the first value of the given key(s), + /// or null if no value found. + /// + public Protoconf.TaskConf.Types.Task? FindFirstTaskExpiry(long expiry) => + FindTaskExpiry(expiry)?.FirstOrDefault(); + + // OrderedIndex: Expiry@SortedTaskExpiry + + /// + /// FindSortedTaskExpiryMap finds the ordered index: key(Expiry@SortedTaskExpiry) to value(Protoconf.TaskConf.Types.Task) sorted map. + /// One key may correspond to multiple values, which are represented by a list. + /// + public ref readonly OrderedIndex_SortedTaskExpiryMap FindSortedTaskExpiryMap() => ref _orderedIndexSortedTaskExpiryMap; + + /// + /// FindSortedTaskExpiry finds a list of all values of the given key(s). + /// + public List? FindSortedTaskExpiry(long expiry) => + _orderedIndexSortedTaskExpiryMap.TryGetValue(expiry, out var value) ? value : null; + + /// + /// FindFirstSortedTaskExpiry finds the first value of the given key(s), + /// or null if no value found. + /// + public Protoconf.TaskConf.Types.Task? FindFirstSortedTaskExpiry(long expiry) => + FindSortedTaskExpiry(expiry)?.FirstOrDefault(); + + // OrderedIndex: (Expiry,ActivityID)@ActivityExpiry + + /// + /// FindActivityExpiryMap finds the ordered index: key((Expiry,ActivityID)@ActivityExpiry) to value(Protoconf.TaskConf.Types.Task) sorted map. + /// One key may correspond to multiple values, which are represented by a list. + /// + public ref readonly OrderedIndex_ActivityExpiryMap FindActivityExpiryMap() => ref _orderedIndexActivityExpiryMap; + + /// + /// FindActivityExpiry finds a list of all values of the given key(s). + /// + public List? FindActivityExpiry(long expiry, long activityId) => + _orderedIndexActivityExpiryMap.TryGetValue(new OrderedIndex_ActivityExpiryKey(expiry, activityId), out var value) ? value : null; + + /// + /// FindFirstActivityExpiry finds the first value of the given key(s), + /// or null if no value found. + /// + public Protoconf.TaskConf.Types.Task? FindFirstActivityExpiry(long expiry, long activityId) => + FindActivityExpiry(expiry, activityId)?.FirstOrDefault(); + } +} diff --git a/test/csharp-tableau-loader/tableau/Util.pc.cs b/test/csharp-tableau-loader/tableau/Util.pc.cs new file mode 100644 index 00000000..d522550f --- /dev/null +++ b/test/csharp-tableau-loader/tableau/Util.pc.cs @@ -0,0 +1,68 @@ +// +// Code generated by protoc-gen-csharp-tableau-loader. DO NOT EDIT. +// versions: +// - protoc-gen-csharp-tableau-loader v0.1.0 +// - protoc v3.19.3 +// +#nullable enable +namespace Tableau +{ + /// + /// Format specifies the format of the configuration file. + /// + public enum Format + { + Unknown, + JSON, + Bin + } + + /// + /// Util provides common utility functions. + /// + public static class Util + { + [ThreadStatic] private static string? _errMsg; + + /// + /// Get the last error message. + /// + public static string GetErrMsg() => _errMsg ?? ""; + + /// + /// Set the error message. + /// + public static void SetErrMsg(string msg) => _errMsg = msg; + + private const string _unknownExt = ".unknown"; + private const string _jsonExt = ".json"; + private const string _binExt = ".binpb"; + + /// + /// GetFormat returns the Format type determined by the file extension of the given path. + /// + public static Format GetFormat(string path) + { + string ext = Path.GetExtension(path); + return ext switch + { + _jsonExt => Format.JSON, + _binExt => Format.Bin, + _ => Format.Unknown, + }; + } + + /// + /// Format2Ext returns the file extension corresponding to the given format. + /// + public static string Format2Ext(Format fmt) + { + return fmt switch + { + Format.JSON => _jsonExt, + Format.Bin => _binExt, + _ => _unknownExt, + }; + } + } +} diff --git a/test/proto/hero_conf.proto b/test/proto/hero_conf.proto index 2437a682..ba636f4b 100644 --- a/test/proto/hero_conf.proto +++ b/test/proto/hero_conf.proto @@ -16,7 +16,7 @@ message HeroConf { ordered_map: true index: "Title" lang_options: { key: "Index" value: "go" } - lang_options: { key: "OrderedMap" value: "cpp" } + lang_options: { key: "OrderedMap" value: "cpp cs" } }; map hero_map = 1 [(tableau.field) = { key: "Name" layout: LAYOUT_VERTICAL }]; message Hero { diff --git a/test/testdata/bin/HeroConf.binpb b/test/testdata/bin/HeroConf.binpb new file mode 100644 index 00000000..58e8445a --- /dev/null +++ b/test/testdata/bin/HeroConf.binpb @@ -0,0 +1,11 @@ + ++ +venus" +venus +title2 +title2attr2 +) +zeus! +zeus +title1 +title1attr1 \ No newline at end of file diff --git a/test/testdata/conf/ItemConf.json b/test/testdata/conf/ItemConf.json index a2b6e2c5..bc09a013 100644 --- a/test/testdata/conf/ItemConf.json +++ b/test/testdata/conf/ItemConf.json @@ -14,6 +14,11 @@ }, "expiry": "2021-01-01T18:00:00Z", "type": "FRUIT_TYPE_APPLE", + "param_list": [ + 1, + 2, + 3 + ], "extTypeList": [ "FRUIT_TYPE_APPLE", "FRUIT_TYPE_ORANGE" @@ -29,7 +34,12 @@ ] }, "expiry": "2022-01-01T08:00:00Z", - "type": "FRUIT_TYPE_ORANGE" + "type": "FRUIT_TYPE_ORANGE", + "param_list": [ + 4, + 5, + 6 + ] }, "3": { "id": 3, @@ -40,7 +50,15 @@ "icon.png" ] }, - "expiry": "2023-01-01T08:00:00Z" + "expiry": "2023-01-01T08:00:00Z", + "param_list": [ + 1, + 2, + 3, + 4, + 5, + 6 + ] }, "2001": { "id": 2001, From 77863b50f899c538e03f02337fc275ed2127e3da Mon Sep 17 00:00:00 2001 From: Kybxd <627940450@qq.com> Date: Fri, 13 Mar 2026 14:45:04 +0800 Subject: [PATCH 2/6] feat: add GitHub Actions workflows for release and testing of C# loader --- .github/workflows/release-csharp.yml | 62 +++++++++++++++++ .github/workflows/testing-csharp.yml | 69 +++++++++++++++++++ .../csharp-tableau-loader.sln | 24 ------- 3 files changed, 131 insertions(+), 24 deletions(-) create mode 100644 .github/workflows/release-csharp.yml create mode 100644 .github/workflows/testing-csharp.yml delete mode 100644 test/csharp-tableau-loader/csharp-tableau-loader.sln diff --git a/.github/workflows/release-csharp.yml b/.github/workflows/release-csharp.yml new file mode 100644 index 00000000..aa718903 --- /dev/null +++ b/.github/workflows/release-csharp.yml @@ -0,0 +1,62 @@ +name: Release C# + +on: + release: + types: [published] + workflow_dispatch: + +jobs: + release: + name: Release cmd/protoc-gen-csharp-tableau-loader + runs-on: ubuntu-latest + if: startsWith(github.event.release.tag_name, 'cmd/protoc-gen-csharp-tableau-loader/') + strategy: + matrix: + goos: [linux, darwin, windows] + goarch: [amd64, arm64] + exclude: + - goos: linux + goarch: arm64 + - goos: windows + goarch: arm64 + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install Go + uses: actions/setup-go@v5 + with: + go-version: "1.21.x" + + - name: Download dependencies + run: | + cd cmd/protoc-gen-csharp-tableau-loader + go mod download + - name: Prepare build directory + run: | + mkdir -p build/ + cp README.md build/ + cp LICENSE build/ + - name: Build + env: + GOOS: ${{ matrix.goos }} + GOARCH: ${{ matrix.goarch }} + run: | + cd cmd/protoc-gen-csharp-tableau-loader + go build -trimpath -o $GITHUB_WORKSPACE/build + - name: Create package + id: package + run: | + PACKAGE_NAME=protoc-gen-csharp-tableau-loader.${GITHUB_REF#refs/tags/cmd/protoc-gen-csharp-tableau-loader/}.${{ matrix.goos }}.${{ matrix.goarch }}.tar.gz + tar -czvf $PACKAGE_NAME -C build . + echo ::set-output name=name::${PACKAGE_NAME} + - name: Upload asset + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ github.event.release.upload_url }} + asset_path: ./${{ steps.package.outputs.name }} + asset_name: ${{ steps.package.outputs.name }} + asset_content_type: application/gzip diff --git a/.github/workflows/testing-csharp.yml b/.github/workflows/testing-csharp.yml new file mode 100644 index 00000000..b2c5fd5c --- /dev/null +++ b/.github/workflows/testing-csharp.yml @@ -0,0 +1,69 @@ +name: Testing C# + +# Trigger on pushes, PRs (excluding documentation changes), and nightly. +on: + push: + branches: [master, main] + pull_request: + schedule: + - cron: 0 0 * * * # daily at 00:00 + workflow_dispatch: + +permissions: + contents: read + +jobs: + test: + strategy: + matrix: + include: + - os: ubuntu-latest + gen_script: bash gen.sh + - os: windows-latest + gen_script: cmd /c gen.bat + + runs-on: ${{ matrix.os }} + timeout-minutes: 10 + + steps: + - name: Checkout Code + uses: actions/checkout@v6 + + - name: Install Go + uses: actions/setup-go@v6 + with: + go-version: 1.21.x + cache: true + + - name: Install .NET SDK + uses: actions/setup-dotnet@v4 + with: + dotnet-version: "8.0.x" + + - name: Install dependencies (Ubuntu) + if: runner.os == 'Linux' + run: sudo apt-get update && sudo apt-get install -y cmake ninja-build + + - name: Install dependencies (Windows) + if: runner.os == 'Windows' + run: choco install cmake ninja -y + + - name: Setup MSVC (Windows) + if: runner.os == 'Windows' + uses: ilammy/msvc-dev-cmd@v1 + + - name: Init submodules and build protobuf + shell: bash + run: bash init.sh + + - name: Generate protoconf + working-directory: test/csharp-tableau-loader + run: ${{ matrix.gen_script }} + + - name: Build + working-directory: test/csharp-tableau-loader + run: dotnet build + + - name: Run loader + working-directory: test/csharp-tableau-loader + run: dotnet run diff --git a/test/csharp-tableau-loader/csharp-tableau-loader.sln b/test/csharp-tableau-loader/csharp-tableau-loader.sln deleted file mode 100644 index d31764f3..00000000 --- a/test/csharp-tableau-loader/csharp-tableau-loader.sln +++ /dev/null @@ -1,24 +0,0 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.5.2.0 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Loader", "Loader.csproj", "{E8053C8C-98ED-7494-D874-8E12A72FA028}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {E8053C8C-98ED-7494-D874-8E12A72FA028}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E8053C8C-98ED-7494-D874-8E12A72FA028}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E8053C8C-98ED-7494-D874-8E12A72FA028}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E8053C8C-98ED-7494-D874-8E12A72FA028}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {80988C2E-15FF-4FCD-B5F0-A75A1AD74321} - EndGlobalSection -EndGlobal From 2609033bff96d45936bd720945113ea006582cc5 Mon Sep 17 00:00:00 2001 From: Kybxd <627940450@qq.com> Date: Fri, 13 Mar 2026 19:43:16 +0800 Subject: [PATCH 3/6] fix: format shell scripts --- test/csharp-tableau-loader/gen.sh | 48 +++++++++++++++---------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/test/csharp-tableau-loader/gen.sh b/test/csharp-tableau-loader/gen.sh index 95826453..7e3d06a9 100644 --- a/test/csharp-tableau-loader/gen.sh +++ b/test/csharp-tableau-loader/gen.sh @@ -10,25 +10,25 @@ cd "$(git rev-parse --show-toplevel)" # Default to locally compiled protoc for local development; fallback to system protoc. LOCAL_PROTOC="./third_party/_submodules/protobuf/cmake/build/protoc" if [ -z "$PROTOC" ]; then - if [ -x "$LOCAL_PROTOC" ]; then - PROTOC="$LOCAL_PROTOC" - else - PROTOC="$(which protoc 2>/dev/null || true)" - fi + if [ -x "$LOCAL_PROTOC" ]; then + PROTOC="$LOCAL_PROTOC" + else + PROTOC="$(which protoc 2>/dev/null || true)" + fi fi if [ -z "$PROTOC" ]; then - echo "Error: protoc not found. Please build protobuf submodule or install protoc." >&2 - exit 1 + echo "Error: protoc not found. Please build protobuf submodule or install protoc." >&2 + exit 1 fi # Allow overriding protobuf include path via environment variable. # Default to local submodule source; fallback to system include path. LOCAL_PROTOBUF_PROTO="./third_party/_submodules/protobuf/src" if [ -z "$PROTOBUF_PROTO" ]; then - if [ -d "$LOCAL_PROTOBUF_PROTO/google/protobuf" ]; then - PROTOBUF_PROTO="$LOCAL_PROTOBUF_PROTO" - else - PROTOBUF_PROTO="$(pkg-config --variable=includedir protobuf 2>/dev/null || echo /usr/include)" - fi + if [ -d "$LOCAL_PROTOBUF_PROTO/google/protobuf" ]; then + PROTOBUF_PROTO="$LOCAL_PROTOBUF_PROTO" + else + PROTOBUF_PROTO="$(pkg-config --variable=includedir protobuf 2>/dev/null || echo /usr/include)" + fi fi TABLEAU_PROTO="./third_party/_submodules/tableau/proto" ROOTDIR="./test/csharp-tableau-loader" @@ -50,13 +50,13 @@ export PATH="$(pwd)/${PLUGIN_DIR}:${PATH}" PROTO_FILES=$(find "$PROTOCONF_IN" -name "*.proto") ${PROTOC} \ - --csharp_out="$PROTOCONF_OUT" \ - --csharp-tableau-loader_out="$LOADER_OUT" \ - --csharp-tableau-loader_opt=paths=source_relative \ - --proto_path="$PROTOBUF_PROTO" \ - --proto_path="$TABLEAU_PROTO" \ - --proto_path="$PROTOCONF_IN" \ - $PROTO_FILES + --csharp_out="$PROTOCONF_OUT" \ + --csharp-tableau-loader_out="$LOADER_OUT" \ + --csharp-tableau-loader_opt=paths=source_relative \ + --proto_path="$PROTOBUF_PROTO" \ + --proto_path="$TABLEAU_PROTO" \ + --proto_path="$PROTOCONF_IN" \ + $PROTO_FILES TABLEAU_IN="$TABLEAU_PROTO/tableau/protobuf" TABLEAU_OUT="${ROOTDIR}/protoconf/tableau" @@ -65,8 +65,8 @@ rm -rfv "$TABLEAU_OUT" mkdir -p "$TABLEAU_OUT" ${PROTOC} \ - --csharp_out="$TABLEAU_OUT" \ - --proto_path="$PROTOBUF_PROTO" \ - --proto_path="$TABLEAU_PROTO" \ - "$TABLEAU_IN/tableau.proto" \ - "$TABLEAU_IN/wellknown.proto" + --csharp_out="$TABLEAU_OUT" \ + --proto_path="$PROTOBUF_PROTO" \ + --proto_path="$TABLEAU_PROTO" \ + "$TABLEAU_IN/tableau.proto" \ + "$TABLEAU_IN/wellknown.proto" From 3a0a7ea645d079b927b46b31cbebb29341d21ee1 Mon Sep 17 00:00:00 2001 From: wenchy Date: Sun, 15 Mar 2026 22:38:47 +0800 Subject: [PATCH 4/6] fix: make generated code compliant with Unity 2022.3 LTS (C# 9) --- README.md | 1 + .../embed/Load.pc.cs | 4 +- .../embed/templates/Hub.pc.cs.tpl | 42 ++++--- .../indexes/index.go | 17 +-- .../indexes/ordered_index.go | 17 +-- .../messager.go | 2 +- .../orderedmap/ordered_map.go | 8 +- test/csharp-tableau-loader/gen.sh | 0 .../tableau/HeroConf.pc.cs | 12 +- test/csharp-tableau-loader/tableau/Hub.pc.cs | 100 ++++++++++------ .../tableau/IndexConf.pc.cs | 108 +++++++++--------- .../tableau/ItemConf.pc.cs | 52 ++++----- test/csharp-tableau-loader/tableau/Load.pc.cs | 4 +- .../tableau/PatchConf.pc.cs | 6 +- .../tableau/TestConf.pc.cs | 88 +++++++------- 15 files changed, 260 insertions(+), 201 deletions(-) mode change 100644 => 100755 test/csharp-tableau-loader/gen.sh diff --git a/README.md b/README.md index 6136676f..396a469c 100644 --- a/README.md +++ b/README.md @@ -68,6 +68,7 @@ The official config loader for [Tableau](https://github.com/tableauio/tableau). ### Requirements +- Unity 2022.3 LTS (C# 9) - dotnet-sdk-8.0 ### Test diff --git a/cmd/protoc-gen-csharp-tableau-loader/embed/Load.pc.cs b/cmd/protoc-gen-csharp-tableau-loader/embed/Load.pc.cs index 23406389..66a835ec 100644 --- a/cmd/protoc-gen-csharp-tableau-loader/embed/Load.pc.cs +++ b/cmd/protoc-gen-csharp-tableau-loader/embed/Load.pc.cs @@ -166,11 +166,11 @@ public object Clone() } /// - /// IMessagerName is an interface that provides the static Name() method for messagers. + /// IMessagerName is an interface that provides the Name() method for messagers. /// public interface IMessagerName { - static abstract string Name(); + string Name(); } /// diff --git a/cmd/protoc-gen-csharp-tableau-loader/embed/templates/Hub.pc.cs.tpl b/cmd/protoc-gen-csharp-tableau-loader/embed/templates/Hub.pc.cs.tpl index 4d08249f..3d0013c3 100644 --- a/cmd/protoc-gen-csharp-tableau-loader/embed/templates/Hub.pc.cs.tpl +++ b/cmd/protoc-gen-csharp-tableau-loader/embed/templates/Hub.pc.cs.tpl @@ -4,19 +4,30 @@ namespace Tableau /// /// MessagerContainer holds all messager instances and provides fast access. /// - internal class MessagerContainer(in Dictionary? messagerMap = null) + internal class MessagerContainer { - public Dictionary MessagerMap = messagerMap ?? []; - public DateTime LastLoadedTime = DateTime.Now; -{{ range . }} public {{ . }}? {{ . }} = InternalGet<{{ . }}>(messagerMap); + public Dictionary MessagerMap; + public DateTime LastLoadedTime; +{{ range . }} public {{ . }}? {{ . }}; {{ end }} + public MessagerContainer(Dictionary? messagerMap = null) + { + MessagerMap = messagerMap ?? new Dictionary(); + LastLoadedTime = DateTime.Now; +{{ range . }} {{ . }} = InternalGet<{{ . }}>(messagerMap); +{{ end }} } + /// /// Get returns the messager of type T from the container. /// - public T? Get() where T : Messager, IMessagerName => InternalGet(MessagerMap); + public T? Get() where T : Messager, IMessagerName, new() => InternalGet(MessagerMap); - private static T? InternalGet(in Dictionary? messagerMap) where T : Messager, IMessagerName => - messagerMap?.TryGetValue(T.Name(), out var messager) == true ? (T)messager : null; + private static T? InternalGet(Dictionary? messagerMap) where T : Messager, IMessagerName, new() + { + if (messagerMap == null) return null; + string name = new T().Name(); + return messagerMap.TryGetValue(name, out var messager) ? (T)messager : null; + } } /// @@ -49,10 +60,15 @@ namespace Tableau /// Hub is the messager manager. It manages loading, accessing, and storing /// all configuration messagers. /// - public class Hub(HubOptions? options = null) + public class Hub { - private readonly Atomic _messagerContainer = new(); - private readonly HubOptions? _options = options; + private readonly Atomic _messagerContainer = new Atomic(); + private readonly HubOptions? _options; + + public Hub(HubOptions? options = null) + { + _options = options; + } /// /// Load fills messages from files in the specified directory and format. @@ -97,7 +113,7 @@ namespace Tableau /// /// Get returns the messager of type T from the hub. /// - public T? Get() where T : Messager, IMessagerName => _messagerContainer.Value?.Get(); + public T? Get() where T : Messager, IMessagerName, new() => _messagerContainer.Value?.Get(); {{ range . }} public {{ . }}? Get{{ . }}() => _messagerContainer.Value?.{{ . }}; {{ end }} @@ -128,12 +144,12 @@ namespace Tableau /// public class Registry { - internal static readonly Dictionary> Registrar = []; + internal static readonly Dictionary> Registrar = new Dictionary>(); /// /// Register registers a messager generator for type T. /// - public static void Register() where T : Messager, IMessagerName, new() => Registrar[T.Name()] = () => new T(); + public static void Register() where T : Messager, IMessagerName, new() => Registrar[new T().Name()] = () => new T(); /// /// Init registers all generated messagers. diff --git a/cmd/protoc-gen-csharp-tableau-loader/indexes/index.go b/cmd/protoc-gen-csharp-tableau-loader/indexes/index.go index b0f9d496..d9ed850e 100644 --- a/cmd/protoc-gen-csharp-tableau-loader/indexes/index.go +++ b/cmd/protoc-gen-csharp-tableau-loader/indexes/index.go @@ -89,14 +89,14 @@ func (x *Generator) genIndexTypeDef() { x.g.P(helper.Indent(2), "public class ", mapType, " : Dictionary<", keyType, ", List<", valueType, ">>;") x.g.P() - x.g.P(helper.Indent(2), "private ", mapType, " ", x.indexContainerName(index, 0), " = [];") + x.g.P(helper.Indent(2), "private ", mapType, " ", x.indexContainerName(index, 0), " = new ", mapType, "();") x.g.P() for i := 1; i < lm.MapDepth; i++ { if i == 1 { - x.g.P(helper.Indent(2), "private Dictionary<", x.keys[0].Type, ", ", mapType, "> ", x.indexContainerName(index, i), " = [];") + x.g.P(helper.Indent(2), "private Dictionary<", x.keys[0].Type, ", ", mapType, "> ", x.indexContainerName(index, i), " = new Dictionary<", x.keys[0].Type, ", ", mapType, ">();") } else { levelIndexKeyType := x.levelKeyType(x.keys[i-1].Fd) - x.g.P(helper.Indent(2), "private Dictionary<", levelIndexKeyType, ", ", mapType, "> ", x.indexContainerName(index, i), " = [];") + x.g.P(helper.Indent(2), "private Dictionary<", levelIndexKeyType, ", ", mapType, "> ", x.indexContainerName(index, i), " = new Dictionary<", levelIndexKeyType, ", ", mapType, ">();") } x.g.P() } @@ -193,18 +193,19 @@ func (x *Generator) generateOneMulticolumnIndex(lm *index.LevelMessage, index *i } func (x *Generator) genLoader(lm *index.LevelMessage, index *index.LevelIndex, ident int, key, parentDataName string) { + valueType := x.mapValueType(index) x.g.P(helper.Indent(ident), "{") x.g.P(helper.Indent(ident+1), "var list = ", x.indexContainerName(index, 0), ".TryGetValue(", key, ", out var existingList) ?") - x.g.P(helper.Indent(ident+1), "existingList : ", x.indexContainerName(index, 0), "[", key, "] = [];") + x.g.P(helper.Indent(ident+1), "existingList : ", x.indexContainerName(index, 0), "[", key, "] = new List<", valueType, ">();") x.g.P(helper.Indent(ident+1), "list.Add(", parentDataName, ");") x.g.P(helper.Indent(ident), "}") for i := 1; i < lm.MapDepth; i++ { x.g.P(helper.Indent(ident), "{") if i == 1 { x.g.P(helper.Indent(ident+1), "var map = ", x.indexContainerName(index, i), ".TryGetValue(k1, out var existingMap) ?") - x.g.P(helper.Indent(ident+1), "existingMap : ", x.indexContainerName(index, i), "[k1] = [];") + x.g.P(helper.Indent(ident+1), "existingMap : ", x.indexContainerName(index, i), "[k1] = new ", x.indexMapType(index), "();") x.g.P(helper.Indent(ident+1), "var list = map.TryGetValue(", key, ", out var existingList) ?") - x.g.P(helper.Indent(ident+1), "existingList : map[", key, "] = [];") + x.g.P(helper.Indent(ident+1), "existingList : map[", key, "] = new List<", valueType, ">();") x.g.P(helper.Indent(ident+1), "list.Add(", parentDataName, ");") } else { var fields []string @@ -214,9 +215,9 @@ func (x *Generator) genLoader(lm *index.LevelMessage, index *index.LevelIndex, i levelIndexKeyType := x.levelKeyType(x.keys[i-1].Fd) x.g.P(helper.Indent(ident+1), "var mapKey = new ", levelIndexKeyType, "(", strings.Join(fields, ", "), ");") x.g.P(helper.Indent(ident+1), "var map = ", x.indexContainerName(index, i), ".TryGetValue(mapKey, out var existingMap) ?") - x.g.P(helper.Indent(ident+1), "existingMap : ", x.indexContainerName(index, i), "[mapKey] = [];") + x.g.P(helper.Indent(ident+1), "existingMap : ", x.indexContainerName(index, i), "[mapKey] = new ", x.indexMapType(index), "();") x.g.P(helper.Indent(ident+1), "var list = map.TryGetValue(", key, ", out var existingList) ?") - x.g.P(helper.Indent(ident+1), "existingList : map[", key, "] = [];") + x.g.P(helper.Indent(ident+1), "existingList : map[", key, "] = new List<", valueType, ">();") x.g.P(helper.Indent(ident+1), "list.Add(", parentDataName, ");") } x.g.P(helper.Indent(ident), "}") diff --git a/cmd/protoc-gen-csharp-tableau-loader/indexes/ordered_index.go b/cmd/protoc-gen-csharp-tableau-loader/indexes/ordered_index.go index 6a13f581..3aa40729 100644 --- a/cmd/protoc-gen-csharp-tableau-loader/indexes/ordered_index.go +++ b/cmd/protoc-gen-csharp-tableau-loader/indexes/ordered_index.go @@ -86,14 +86,14 @@ func (x *Generator) genOrderedIndexTypeDef() { x.g.P(helper.Indent(2), "public class ", mapType, " : SortedDictionary<", keyType, ", List<", valueType, ">>;") x.g.P() - x.g.P(helper.Indent(2), "private ", mapType, " ", x.orderedIndexContainerName(index, 0), " = [];") + x.g.P(helper.Indent(2), "private ", mapType, " ", x.orderedIndexContainerName(index, 0), " = new ", mapType, "();") x.g.P() for i := 1; i < lm.MapDepth; i++ { if i == 1 { - x.g.P(helper.Indent(2), "private Dictionary<", x.keys[0].Type, ", ", mapType, "> ", x.orderedIndexContainerName(index, i), " = [];") + x.g.P(helper.Indent(2), "private Dictionary<", x.keys[0].Type, ", ", mapType, "> ", x.orderedIndexContainerName(index, i), " = new Dictionary<", x.keys[0].Type, ", ", mapType, ">();") } else { levelIndexKeyType := x.levelKeyType(x.keys[i-1].Fd) - x.g.P(helper.Indent(2), "private Dictionary<", levelIndexKeyType, ", ", mapType, "> ", x.orderedIndexContainerName(index, i), " = [];") + x.g.P(helper.Indent(2), "private Dictionary<", levelIndexKeyType, ", ", mapType, "> ", x.orderedIndexContainerName(index, i), " = new Dictionary<", levelIndexKeyType, ", ", mapType, ">();") } x.g.P() } @@ -190,18 +190,19 @@ func (x *Generator) generateOneMulticolumnOrderedIndex(lm *index.LevelMessage, i } func (x *Generator) genOrderedIndexLoaderCommon(lm *index.LevelMessage, index *index.LevelIndex, ident int, key, parentDataName string) { + valueType := x.mapValueType(index) x.g.P(helper.Indent(ident), "{") x.g.P(helper.Indent(ident+1), "var list = ", x.orderedIndexContainerName(index, 0), ".TryGetValue(", key, ", out var existingList) ?") - x.g.P(helper.Indent(ident+1), "existingList : ", x.orderedIndexContainerName(index, 0), "[", key, "] = [];") + x.g.P(helper.Indent(ident+1), "existingList : ", x.orderedIndexContainerName(index, 0), "[", key, "] = new List<", valueType, ">();") x.g.P(helper.Indent(ident+1), "list.Add(", parentDataName, ");") x.g.P(helper.Indent(ident), "}") for i := 1; i < lm.MapDepth; i++ { x.g.P(helper.Indent(ident), "{") if i == 1 { x.g.P(helper.Indent(ident+1), "var map = ", x.orderedIndexContainerName(index, i), ".TryGetValue(k1, out var existingMap) ?") - x.g.P(helper.Indent(ident+1), "existingMap : ", x.orderedIndexContainerName(index, i), "[k1] = [];") + x.g.P(helper.Indent(ident+1), "existingMap : ", x.orderedIndexContainerName(index, i), "[k1] = new ", x.orderedIndexMapType(index), "();") x.g.P(helper.Indent(ident+1), "var list = map.TryGetValue(", key, ", out var existingList) ?") - x.g.P(helper.Indent(ident+1), "existingList : map[", key, "] = [];") + x.g.P(helper.Indent(ident+1), "existingList : map[", key, "] = new List<", valueType, ">();") x.g.P(helper.Indent(ident+1), "list.Add(", parentDataName, ");") } else { var fields []string @@ -211,9 +212,9 @@ func (x *Generator) genOrderedIndexLoaderCommon(lm *index.LevelMessage, index *i levelIndexKeyType := x.levelKeyType(x.keys[i-1].Fd) x.g.P(helper.Indent(ident+1), "var mapKey = new ", levelIndexKeyType, "(", strings.Join(fields, ", "), ");") x.g.P(helper.Indent(ident+1), "var map = ", x.orderedIndexContainerName(index, i), ".TryGetValue(mapKey, out var existingMap) ?") - x.g.P(helper.Indent(ident+1), "existingMap : ", x.orderedIndexContainerName(index, i), "[mapKey] = [];") + x.g.P(helper.Indent(ident+1), "existingMap : ", x.orderedIndexContainerName(index, i), "[mapKey] = new ", x.orderedIndexMapType(index), "();") x.g.P(helper.Indent(ident+1), "var list = map.TryGetValue(", key, ", out var existingList) ?") - x.g.P(helper.Indent(ident+1), "existingList : map[", key, "] = [];") + x.g.P(helper.Indent(ident+1), "existingList : map[", key, "] = new List<", valueType, ">();") x.g.P(helper.Indent(ident+1), "list.Add(", parentDataName, ");") } x.g.P(helper.Indent(ident), "}") diff --git a/cmd/protoc-gen-csharp-tableau-loader/messager.go b/cmd/protoc-gen-csharp-tableau-loader/messager.go index d51949ae..38135b9f 100644 --- a/cmd/protoc-gen-csharp-tableau-loader/messager.go +++ b/cmd/protoc-gen-csharp-tableau-loader/messager.go @@ -75,7 +75,7 @@ func genMessage(gen *protogen.Plugin, g *protogen.GeneratedFile, message *protog g.P(helper.Indent(2), "/// ") g.P(helper.Indent(2), "/// Name returns the ", messagerName, "'s message name.") g.P(helper.Indent(2), "/// ") - g.P(helper.Indent(2), "public static string Name() => ", helper.ParseCsharpClassType(message.Desc), ".Descriptor.Name;") + g.P(helper.Indent(2), "public string Name() => ", helper.ParseCsharpClassType(message.Desc), ".Descriptor.Name;") g.P() g.P(helper.Indent(2), "/// ") g.P(helper.Indent(2), "/// Load loads ", messagerName, "'s content in the given dir, based on format and messager options.") diff --git a/cmd/protoc-gen-csharp-tableau-loader/orderedmap/ordered_map.go b/cmd/protoc-gen-csharp-tableau-loader/orderedmap/ordered_map.go index 6bcb2d2b..94ad0172 100644 --- a/cmd/protoc-gen-csharp-tableau-loader/orderedmap/ordered_map.go +++ b/cmd/protoc-gen-csharp-tableau-loader/orderedmap/ordered_map.go @@ -70,13 +70,15 @@ func (x *Generator) genOrderedMapTypeDef(md protoreflect.MessageDescriptor, dept if nextMapFD != nil { currValueType := helper.ParseCsharpType(fd.MapValue()) nextOrderedMap := x.mapType(nextMapFD) - x.g.P(helper.Indent(2), "public class ", orderedMapValue, "(", nextOrderedMap, " item1, ", currValueType, " item2)") - x.g.P(helper.Indent(3), ": Tuple<", nextOrderedMap, ", ", currValueType, ">(item1, item2);") + x.g.P(helper.Indent(2), "public class ", orderedMapValue, " : Tuple<", nextOrderedMap, ", ", currValueType, ">") + x.g.P(helper.Indent(2), "{") + x.g.P(helper.Indent(3), "public ", orderedMapValue, "(", nextOrderedMap, " item1, ", currValueType, " item2) : base(item1, item2) { }") + x.g.P(helper.Indent(2), "}") } x.g.P(helper.Indent(2), "public class ", orderedMap, " : SortedDictionary<", keyType, ", ", x.mapValueFieldType(fd), ">;") x.g.P() if depth == 1 { - x.g.P(helper.Indent(2), "private ", orderedMap, " _orderedMap = [];") + x.g.P(helper.Indent(2), "private ", orderedMap, " _orderedMap = new ", orderedMap, "();") x.g.P() } break diff --git a/test/csharp-tableau-loader/gen.sh b/test/csharp-tableau-loader/gen.sh old mode 100644 new mode 100755 diff --git a/test/csharp-tableau-loader/tableau/HeroConf.pc.cs b/test/csharp-tableau-loader/tableau/HeroConf.pc.cs index 0a5118e5..9fd1f5ca 100644 --- a/test/csharp-tableau-loader/tableau/HeroConf.pc.cs +++ b/test/csharp-tableau-loader/tableau/HeroConf.pc.cs @@ -17,18 +17,20 @@ public class HeroConf : Messager, IMessagerName // OrderedMap types. public class OrderedMap_Hero_AttrMap : SortedDictionary; - public class OrderedMap_HeroValue(OrderedMap_Hero_AttrMap item1, Protoconf.HeroConf.Types.Hero item2) - : Tuple(item1, item2); + public class OrderedMap_HeroValue : Tuple + { + public OrderedMap_HeroValue(OrderedMap_Hero_AttrMap item1, Protoconf.HeroConf.Types.Hero item2) : base(item1, item2) { } + } public class OrderedMap_HeroMap : SortedDictionary; - private OrderedMap_HeroMap _orderedMap = []; + private OrderedMap_HeroMap _orderedMap = new OrderedMap_HeroMap(); private Protoconf.HeroConf _data = new(); /// /// Name returns the HeroConf's message name. /// - public static string Name() => Protoconf.HeroConf.Descriptor.Name; + public string Name() => Protoconf.HeroConf.Descriptor.Name; /// /// Load loads HeroConf's content in the given dir, based on format and messager options. @@ -122,7 +124,7 @@ public class HeroBaseConf : Messager, IMessagerName /// /// Name returns the HeroBaseConf's message name. /// - public static string Name() => Protoconf.HeroBaseConf.Descriptor.Name; + public string Name() => Protoconf.HeroBaseConf.Descriptor.Name; /// /// Load loads HeroBaseConf's content in the given dir, based on format and messager options. diff --git a/test/csharp-tableau-loader/tableau/Hub.pc.cs b/test/csharp-tableau-loader/tableau/Hub.pc.cs index c95fbebf..755a36c1 100644 --- a/test/csharp-tableau-loader/tableau/Hub.pc.cs +++ b/test/csharp-tableau-loader/tableau/Hub.pc.cs @@ -11,33 +11,58 @@ namespace Tableau /// /// MessagerContainer holds all messager instances and provides fast access. /// - internal class MessagerContainer(in Dictionary? messagerMap = null) + internal class MessagerContainer { - public Dictionary MessagerMap = messagerMap ?? []; - public DateTime LastLoadedTime = DateTime.Now; - public ItemConf? ItemConf = InternalGet(messagerMap); - public ActivityConf? ActivityConf = InternalGet(messagerMap); - public ChapterConf? ChapterConf = InternalGet(messagerMap); - public ThemeConf? ThemeConf = InternalGet(messagerMap); - public TaskConf? TaskConf = InternalGet(messagerMap); - public FruitConf? FruitConf = InternalGet(messagerMap); - public Fruit2Conf? Fruit2Conf = InternalGet(messagerMap); - public Fruit3Conf? Fruit3Conf = InternalGet(messagerMap); - public Fruit4Conf? Fruit4Conf = InternalGet(messagerMap); - public Fruit5Conf? Fruit5Conf = InternalGet(messagerMap); - public HeroConf? HeroConf = InternalGet(messagerMap); - public HeroBaseConf? HeroBaseConf = InternalGet(messagerMap); - public PatchReplaceConf? PatchReplaceConf = InternalGet(messagerMap); - public PatchMergeConf? PatchMergeConf = InternalGet(messagerMap); - public RecursivePatchConf? RecursivePatchConf = InternalGet(messagerMap); + public Dictionary MessagerMap; + public DateTime LastLoadedTime; + public ActivityConf? ActivityConf; + public ChapterConf? ChapterConf; + public ThemeConf? ThemeConf; + public TaskConf? TaskConf; + public FruitConf? FruitConf; + public Fruit2Conf? Fruit2Conf; + public Fruit3Conf? Fruit3Conf; + public Fruit4Conf? Fruit4Conf; + public Fruit5Conf? Fruit5Conf; + public ItemConf? ItemConf; + public PatchReplaceConf? PatchReplaceConf; + public PatchMergeConf? PatchMergeConf; + public RecursivePatchConf? RecursivePatchConf; + public HeroConf? HeroConf; + public HeroBaseConf? HeroBaseConf; + + public MessagerContainer(Dictionary? messagerMap = null) + { + MessagerMap = messagerMap ?? new Dictionary(); + LastLoadedTime = DateTime.Now; + ActivityConf = InternalGet(messagerMap); + ChapterConf = InternalGet(messagerMap); + ThemeConf = InternalGet(messagerMap); + TaskConf = InternalGet(messagerMap); + FruitConf = InternalGet(messagerMap); + Fruit2Conf = InternalGet(messagerMap); + Fruit3Conf = InternalGet(messagerMap); + Fruit4Conf = InternalGet(messagerMap); + Fruit5Conf = InternalGet(messagerMap); + ItemConf = InternalGet(messagerMap); + PatchReplaceConf = InternalGet(messagerMap); + PatchMergeConf = InternalGet(messagerMap); + RecursivePatchConf = InternalGet(messagerMap); + HeroConf = InternalGet(messagerMap); + HeroBaseConf = InternalGet(messagerMap); + } /// /// Get returns the messager of type T from the container. /// - public T? Get() where T : Messager, IMessagerName => InternalGet(MessagerMap); + public T? Get() where T : Messager, IMessagerName, new() => InternalGet(MessagerMap); - private static T? InternalGet(in Dictionary? messagerMap) where T : Messager, IMessagerName => - messagerMap?.TryGetValue(T.Name(), out var messager) == true ? (T)messager : null; + private static T? InternalGet(Dictionary? messagerMap) where T : Messager, IMessagerName, new() + { + if (messagerMap == null) return null; + string name = new T().Name(); + return messagerMap.TryGetValue(name, out var messager) ? (T)messager : null; + } } /// @@ -70,10 +95,15 @@ public class HubOptions /// Hub is the messager manager. It manages loading, accessing, and storing /// all configuration messagers. /// - public class Hub(HubOptions? options = null) + public class Hub { - private readonly Atomic _messagerContainer = new(); - private readonly HubOptions? _options = options; + private readonly Atomic _messagerContainer = new Atomic(); + private readonly HubOptions? _options; + + public Hub(HubOptions? options = null) + { + _options = options; + } /// /// Load fills messages from files in the specified directory and format. @@ -118,9 +148,7 @@ public bool Load(string dir, Format fmt, in Load.Options? options = null) /// /// Get returns the messager of type T from the hub. /// - public T? Get() where T : Messager, IMessagerName => _messagerContainer.Value?.Get(); - - public ItemConf? GetItemConf() => _messagerContainer.Value?.ItemConf; + public T? Get() where T : Messager, IMessagerName, new() => _messagerContainer.Value?.Get(); public ActivityConf? GetActivityConf() => _messagerContainer.Value?.ActivityConf; @@ -140,9 +168,7 @@ public bool Load(string dir, Format fmt, in Load.Options? options = null) public Fruit5Conf? GetFruit5Conf() => _messagerContainer.Value?.Fruit5Conf; - public HeroConf? GetHeroConf() => _messagerContainer.Value?.HeroConf; - - public HeroBaseConf? GetHeroBaseConf() => _messagerContainer.Value?.HeroBaseConf; + public ItemConf? GetItemConf() => _messagerContainer.Value?.ItemConf; public PatchReplaceConf? GetPatchReplaceConf() => _messagerContainer.Value?.PatchReplaceConf; @@ -150,6 +176,10 @@ public bool Load(string dir, Format fmt, in Load.Options? options = null) public RecursivePatchConf? GetRecursivePatchConf() => _messagerContainer.Value?.RecursivePatchConf; + public HeroConf? GetHeroConf() => _messagerContainer.Value?.HeroConf; + + public HeroBaseConf? GetHeroBaseConf() => _messagerContainer.Value?.HeroBaseConf; + /// /// GetLastLoadedTime returns the time when hub's messager container was last set. /// @@ -177,19 +207,18 @@ private Dictionary NewMessagerMap() /// public class Registry { - internal static readonly Dictionary> Registrar = []; + internal static readonly Dictionary> Registrar = new Dictionary>(); /// /// Register registers a messager generator for type T. /// - public static void Register() where T : Messager, IMessagerName, new() => Registrar[T.Name()] = () => new T(); + public static void Register() where T : Messager, IMessagerName, new() => Registrar[new T().Name()] = () => new T(); /// /// Init registers all generated messagers. /// public static void Init() { - Register(); Register(); Register(); Register(); @@ -199,11 +228,12 @@ public static void Init() Register(); Register(); Register(); - Register(); - Register(); + Register(); Register(); Register(); Register(); + Register(); + Register(); } } } diff --git a/test/csharp-tableau-loader/tableau/IndexConf.pc.cs b/test/csharp-tableau-loader/tableau/IndexConf.pc.cs index 149e4ce7..32d5fdcc 100644 --- a/test/csharp-tableau-loader/tableau/IndexConf.pc.cs +++ b/test/csharp-tableau-loader/tableau/IndexConf.pc.cs @@ -18,16 +18,16 @@ public class FruitConf : Messager, IMessagerName // OrderedIndex: Price public class OrderedIndex_ItemMap : SortedDictionary>; - private OrderedIndex_ItemMap _orderedIndexItemMap = []; + private OrderedIndex_ItemMap _orderedIndexItemMap = new OrderedIndex_ItemMap(); - private Dictionary _orderedIndexItemMap1 = []; + private Dictionary _orderedIndexItemMap1 = new Dictionary(); private Protoconf.FruitConf _data = new(); /// /// Name returns the FruitConf's message name. /// - public static string Name() => Protoconf.FruitConf.Descriptor.Name; + public string Name() => Protoconf.FruitConf.Descriptor.Name; /// /// Load loads FruitConf's content in the given dir, based on format and messager options. @@ -82,14 +82,14 @@ protected override bool ProcessAfterLoad() var key = item2.Value.Price; { var list = _orderedIndexItemMap.TryGetValue(key, out var existingList) ? - existingList : _orderedIndexItemMap[key] = []; + existingList : _orderedIndexItemMap[key] = new List(); list.Add(item2.Value); } { var map = _orderedIndexItemMap1.TryGetValue(k1, out var existingMap) ? - existingMap : _orderedIndexItemMap1[k1] = []; + existingMap : _orderedIndexItemMap1[k1] = new OrderedIndex_ItemMap(); var list = map.TryGetValue(key, out var existingList) ? - existingList : map[key] = []; + existingList : map[key] = new List(); list.Add(item2.Value); } } @@ -179,29 +179,29 @@ public class Fruit2Conf : Messager, IMessagerName // Index: CountryName public class Index_CountryMap : Dictionary>; - private Index_CountryMap _indexCountryMap = []; + private Index_CountryMap _indexCountryMap = new Index_CountryMap(); // Index: CountryItemAttrName public class Index_AttrMap : Dictionary>; - private Index_AttrMap _indexAttrMap = []; + private Index_AttrMap _indexAttrMap = new Index_AttrMap(); - private Dictionary _indexAttrMap1 = []; + private Dictionary _indexAttrMap1 = new Dictionary(); // OrderedIndex types. // OrderedIndex: CountryItemPrice public class OrderedIndex_ItemMap : SortedDictionary>; - private OrderedIndex_ItemMap _orderedIndexItemMap = []; + private OrderedIndex_ItemMap _orderedIndexItemMap = new OrderedIndex_ItemMap(); - private Dictionary _orderedIndexItemMap1 = []; + private Dictionary _orderedIndexItemMap1 = new Dictionary(); private Protoconf.Fruit2Conf _data = new(); /// /// Name returns the Fruit2Conf's message name. /// - public static string Name() => Protoconf.Fruit2Conf.Descriptor.Name; + public string Name() => Protoconf.Fruit2Conf.Descriptor.Name; /// /// Load loads Fruit2Conf's content in the given dir, based on format and messager options. @@ -257,7 +257,7 @@ protected override bool ProcessAfterLoad() var key = item2.Name; { var list = _indexCountryMap.TryGetValue(key, out var existingList) ? - existingList : _indexCountryMap[key] = []; + existingList : _indexCountryMap[key] = new List(); list.Add(item2); } } @@ -270,14 +270,14 @@ protected override bool ProcessAfterLoad() var key = item4.Name; { var list = _indexAttrMap.TryGetValue(key, out var existingList) ? - existingList : _indexAttrMap[key] = []; + existingList : _indexAttrMap[key] = new List(); list.Add(item4); } { var map = _indexAttrMap1.TryGetValue(k1, out var existingMap) ? - existingMap : _indexAttrMap1[k1] = []; + existingMap : _indexAttrMap1[k1] = new Index_AttrMap(); var list = map.TryGetValue(key, out var existingList) ? - existingList : map[key] = []; + existingList : map[key] = new List(); list.Add(item4); } } @@ -300,14 +300,14 @@ protected override bool ProcessAfterLoad() var key = item3.Value.Price; { var list = _orderedIndexItemMap.TryGetValue(key, out var existingList) ? - existingList : _orderedIndexItemMap[key] = []; + existingList : _orderedIndexItemMap[key] = new List(); list.Add(item3.Value); } { var map = _orderedIndexItemMap1.TryGetValue(k1, out var existingMap) ? - existingMap : _orderedIndexItemMap1[k1] = []; + existingMap : _orderedIndexItemMap1[k1] = new OrderedIndex_ItemMap(); var list = map.TryGetValue(key, out var existingList) ? - existingList : map[key] = []; + existingList : map[key] = new List(); list.Add(item3.Value); } } @@ -455,25 +455,25 @@ public class Fruit3Conf : Messager, IMessagerName // Index: CountryName public class Index_CountryMap : Dictionary>; - private Index_CountryMap _indexCountryMap = []; + private Index_CountryMap _indexCountryMap = new Index_CountryMap(); // Index: CountryItemAttrName public class Index_AttrMap : Dictionary>; - private Index_AttrMap _indexAttrMap = []; + private Index_AttrMap _indexAttrMap = new Index_AttrMap(); // OrderedIndex types. // OrderedIndex: CountryItemPrice public class OrderedIndex_ItemMap : SortedDictionary>; - private OrderedIndex_ItemMap _orderedIndexItemMap = []; + private OrderedIndex_ItemMap _orderedIndexItemMap = new OrderedIndex_ItemMap(); private Protoconf.Fruit3Conf _data = new(); /// /// Name returns the Fruit3Conf's message name. /// - public static string Name() => Protoconf.Fruit3Conf.Descriptor.Name; + public string Name() => Protoconf.Fruit3Conf.Descriptor.Name; /// /// Load loads Fruit3Conf's content in the given dir, based on format and messager options. @@ -527,7 +527,7 @@ protected override bool ProcessAfterLoad() var key = item2.Name; { var list = _indexCountryMap.TryGetValue(key, out var existingList) ? - existingList : _indexCountryMap[key] = []; + existingList : _indexCountryMap[key] = new List(); list.Add(item2); } } @@ -540,7 +540,7 @@ protected override bool ProcessAfterLoad() var key = item4.Name; { var list = _indexAttrMap.TryGetValue(key, out var existingList) ? - existingList : _indexAttrMap[key] = []; + existingList : _indexAttrMap[key] = new List(); list.Add(item4); } } @@ -561,7 +561,7 @@ protected override bool ProcessAfterLoad() var key = item3.Value.Price; { var list = _orderedIndexItemMap.TryGetValue(key, out var existingList) ? - existingList : _orderedIndexItemMap[key] = []; + existingList : _orderedIndexItemMap[key] = new List(); list.Add(item3.Value); } } @@ -671,35 +671,35 @@ public override int GetHashCode() => // Index: CountryName public class Index_CountryMap : Dictionary>; - private Index_CountryMap _indexCountryMap = []; + private Index_CountryMap _indexCountryMap = new Index_CountryMap(); - private Dictionary _indexCountryMap1 = []; + private Dictionary _indexCountryMap1 = new Dictionary(); // Index: CountryItemAttrName public class Index_AttrMap : Dictionary>; - private Index_AttrMap _indexAttrMap = []; + private Index_AttrMap _indexAttrMap = new Index_AttrMap(); - private Dictionary _indexAttrMap1 = []; + private Dictionary _indexAttrMap1 = new Dictionary(); - private Dictionary _indexAttrMap2 = []; + private Dictionary _indexAttrMap2 = new Dictionary(); // OrderedIndex types. // OrderedIndex: CountryItemPrice public class OrderedIndex_ItemMap : SortedDictionary>; - private OrderedIndex_ItemMap _orderedIndexItemMap = []; + private OrderedIndex_ItemMap _orderedIndexItemMap = new OrderedIndex_ItemMap(); - private Dictionary _orderedIndexItemMap1 = []; + private Dictionary _orderedIndexItemMap1 = new Dictionary(); - private Dictionary _orderedIndexItemMap2 = []; + private Dictionary _orderedIndexItemMap2 = new Dictionary(); private Protoconf.Fruit4Conf _data = new(); /// /// Name returns the Fruit4Conf's message name. /// - public static string Name() => Protoconf.Fruit4Conf.Descriptor.Name; + public string Name() => Protoconf.Fruit4Conf.Descriptor.Name; /// /// Load loads Fruit4Conf's content in the given dir, based on format and messager options. @@ -758,14 +758,14 @@ protected override bool ProcessAfterLoad() var key = item2.Value.Name; { var list = _indexCountryMap.TryGetValue(key, out var existingList) ? - existingList : _indexCountryMap[key] = []; + existingList : _indexCountryMap[key] = new List(); list.Add(item2.Value); } { var map = _indexCountryMap1.TryGetValue(k1, out var existingMap) ? - existingMap : _indexCountryMap1[k1] = []; + existingMap : _indexCountryMap1[k1] = new Index_CountryMap(); var list = map.TryGetValue(key, out var existingList) ? - existingList : map[key] = []; + existingList : map[key] = new List(); list.Add(item2.Value); } } @@ -778,22 +778,22 @@ protected override bool ProcessAfterLoad() var key = item4.Name; { var list = _indexAttrMap.TryGetValue(key, out var existingList) ? - existingList : _indexAttrMap[key] = []; + existingList : _indexAttrMap[key] = new List(); list.Add(item4); } { var map = _indexAttrMap1.TryGetValue(k1, out var existingMap) ? - existingMap : _indexAttrMap1[k1] = []; + existingMap : _indexAttrMap1[k1] = new Index_AttrMap(); var list = map.TryGetValue(key, out var existingList) ? - existingList : map[key] = []; + existingList : map[key] = new List(); list.Add(item4); } { var mapKey = new LevelIndex_Fruit_CountryKey(k1, k2); var map = _indexAttrMap2.TryGetValue(mapKey, out var existingMap) ? - existingMap : _indexAttrMap2[mapKey] = []; + existingMap : _indexAttrMap2[mapKey] = new Index_AttrMap(); var list = map.TryGetValue(key, out var existingList) ? - existingList : map[key] = []; + existingList : map[key] = new List(); list.Add(item4); } } @@ -818,22 +818,22 @@ protected override bool ProcessAfterLoad() var key = item3.Value.Price; { var list = _orderedIndexItemMap.TryGetValue(key, out var existingList) ? - existingList : _orderedIndexItemMap[key] = []; + existingList : _orderedIndexItemMap[key] = new List(); list.Add(item3.Value); } { var map = _orderedIndexItemMap1.TryGetValue(k1, out var existingMap) ? - existingMap : _orderedIndexItemMap1[k1] = []; + existingMap : _orderedIndexItemMap1[k1] = new OrderedIndex_ItemMap(); var list = map.TryGetValue(key, out var existingList) ? - existingList : map[key] = []; + existingList : map[key] = new List(); list.Add(item3.Value); } { var mapKey = new LevelIndex_Fruit_CountryKey(k1, k2); var map = _orderedIndexItemMap2.TryGetValue(mapKey, out var existingMap) ? - existingMap : _orderedIndexItemMap2[mapKey] = []; + existingMap : _orderedIndexItemMap2[mapKey] = new OrderedIndex_ItemMap(); var list = map.TryGetValue(key, out var existingList) ? - existingList : map[key] = []; + existingList : map[key] = new List(); list.Add(item3.Value); } } @@ -1068,16 +1068,16 @@ public class Fruit5Conf : Messager, IMessagerName // Index: CountryName public class Index_CountryMap : Dictionary>; - private Index_CountryMap _indexCountryMap = []; + private Index_CountryMap _indexCountryMap = new Index_CountryMap(); - private Dictionary _indexCountryMap1 = []; + private Dictionary _indexCountryMap1 = new Dictionary(); private Protoconf.Fruit5Conf _data = new(); /// /// Name returns the Fruit5Conf's message name. /// - public static string Name() => Protoconf.Fruit5Conf.Descriptor.Name; + public string Name() => Protoconf.Fruit5Conf.Descriptor.Name; /// /// Load loads Fruit5Conf's content in the given dir, based on format and messager options. @@ -1132,14 +1132,14 @@ protected override bool ProcessAfterLoad() var key = item2.Value.Name; { var list = _indexCountryMap.TryGetValue(key, out var existingList) ? - existingList : _indexCountryMap[key] = []; + existingList : _indexCountryMap[key] = new List(); list.Add(item2.Value); } { var map = _indexCountryMap1.TryGetValue(k1, out var existingMap) ? - existingMap : _indexCountryMap1[k1] = []; + existingMap : _indexCountryMap1[k1] = new Index_CountryMap(); var list = map.TryGetValue(key, out var existingList) ? - existingList : map[key] = []; + existingList : map[key] = new List(); list.Add(item2.Value); } } diff --git a/test/csharp-tableau-loader/tableau/ItemConf.pc.cs b/test/csharp-tableau-loader/tableau/ItemConf.pc.cs index 42497658..fa73ae8c 100644 --- a/test/csharp-tableau-loader/tableau/ItemConf.pc.cs +++ b/test/csharp-tableau-loader/tableau/ItemConf.pc.cs @@ -17,28 +17,28 @@ public class ItemConf : Messager, IMessagerName // OrderedMap types. public class OrderedMap_ItemMap : SortedDictionary; - private OrderedMap_ItemMap _orderedMap = []; + private OrderedMap_ItemMap _orderedMap = new OrderedMap_ItemMap(); // Index types. // Index: Type public class Index_ItemMap : Dictionary>; - private Index_ItemMap _indexItemMap = []; + private Index_ItemMap _indexItemMap = new Index_ItemMap(); // Index: Param@ItemInfo public class Index_ItemInfoMap : Dictionary>; - private Index_ItemInfoMap _indexItemInfoMap = []; + private Index_ItemInfoMap _indexItemInfoMap = new Index_ItemInfoMap(); // Index: Default@ItemDefaultInfo public class Index_ItemDefaultInfoMap : Dictionary>; - private Index_ItemDefaultInfoMap _indexItemDefaultInfoMap = []; + private Index_ItemDefaultInfoMap _indexItemDefaultInfoMap = new Index_ItemDefaultInfoMap(); // Index: ExtType@ItemExtInfo public class Index_ItemExtInfoMap : Dictionary>; - private Index_ItemExtInfoMap _indexItemExtInfoMap = []; + private Index_ItemExtInfoMap _indexItemExtInfoMap = new Index_ItemExtInfoMap(); // Index: (ID,Name)@AwardItem public readonly struct Index_AwardItemKey : IEquatable @@ -61,7 +61,7 @@ public override int GetHashCode() => public class Index_AwardItemMap : Dictionary>; - private Index_AwardItemMap _indexAwardItemMap = []; + private Index_AwardItemMap _indexAwardItemMap = new Index_AwardItemMap(); // Index: (ID,Type,Param,ExtType)@SpecialItem public readonly struct Index_SpecialItemKey : IEquatable @@ -88,33 +88,33 @@ public override int GetHashCode() => public class Index_SpecialItemMap : Dictionary>; - private Index_SpecialItemMap _indexSpecialItemMap = []; + private Index_SpecialItemMap _indexSpecialItemMap = new Index_SpecialItemMap(); // Index: PathDir@ItemPathDir public class Index_ItemPathDirMap : Dictionary>; - private Index_ItemPathDirMap _indexItemPathDirMap = []; + private Index_ItemPathDirMap _indexItemPathDirMap = new Index_ItemPathDirMap(); // Index: PathName@ItemPathName public class Index_ItemPathNameMap : Dictionary>; - private Index_ItemPathNameMap _indexItemPathNameMap = []; + private Index_ItemPathNameMap _indexItemPathNameMap = new Index_ItemPathNameMap(); // Index: PathFriendID@ItemPathFriendID public class Index_ItemPathFriendIDMap : Dictionary>; - private Index_ItemPathFriendIDMap _indexItemPathFriendIdMap = []; + private Index_ItemPathFriendIDMap _indexItemPathFriendIdMap = new Index_ItemPathFriendIDMap(); // Index: UseEffectType@UseEffectType public class Index_UseEffectTypeMap : Dictionary>; - private Index_UseEffectTypeMap _indexUseEffectTypeMap = []; + private Index_UseEffectTypeMap _indexUseEffectTypeMap = new Index_UseEffectTypeMap(); // OrderedIndex types. // OrderedIndex: ExtType@ExtType public class OrderedIndex_ExtTypeMap : SortedDictionary>; - private OrderedIndex_ExtTypeMap _orderedIndexExtTypeMap = []; + private OrderedIndex_ExtTypeMap _orderedIndexExtTypeMap = new OrderedIndex_ExtTypeMap(); // OrderedIndex: (Param,ExtType)@ParamExtType public readonly struct OrderedIndex_ParamExtTypeKey : IComparable @@ -134,14 +134,14 @@ public int CompareTo(OrderedIndex_ParamExtTypeKey other) => public class OrderedIndex_ParamExtTypeMap : SortedDictionary>; - private OrderedIndex_ParamExtTypeMap _orderedIndexParamExtTypeMap = []; + private OrderedIndex_ParamExtTypeMap _orderedIndexParamExtTypeMap = new OrderedIndex_ParamExtTypeMap(); private Protoconf.ItemConf _data = new(); /// /// Name returns the ItemConf's message name. /// - public static string Name() => Protoconf.ItemConf.Descriptor.Name; + public string Name() => Protoconf.ItemConf.Descriptor.Name; /// /// Load loads ItemConf's content in the given dir, based on format and messager options. @@ -207,7 +207,7 @@ protected override bool ProcessAfterLoad() var key = item1.Value.Type; { var list = _indexItemMap.TryGetValue(key, out var existingList) ? - existingList : _indexItemMap[key] = []; + existingList : _indexItemMap[key] = new List(); list.Add(item1.Value); } } @@ -217,7 +217,7 @@ protected override bool ProcessAfterLoad() { { var list = _indexItemInfoMap.TryGetValue(item2, out var existingList) ? - existingList : _indexItemInfoMap[item2] = []; + existingList : _indexItemInfoMap[item2] = new List(); list.Add(item1.Value); } } @@ -227,7 +227,7 @@ protected override bool ProcessAfterLoad() var key = item1.Value.Default; { var list = _indexItemDefaultInfoMap.TryGetValue(key, out var existingList) ? - existingList : _indexItemDefaultInfoMap[key] = []; + existingList : _indexItemDefaultInfoMap[key] = new List(); list.Add(item1.Value); } } @@ -237,7 +237,7 @@ protected override bool ProcessAfterLoad() { { var list = _indexItemExtInfoMap.TryGetValue(item2, out var existingList) ? - existingList : _indexItemExtInfoMap[item2] = []; + existingList : _indexItemExtInfoMap[item2] = new List(); list.Add(item1.Value); } } @@ -247,7 +247,7 @@ protected override bool ProcessAfterLoad() var key = new Index_AwardItemKey(item1.Value.Id, item1.Value.Name); { var list = _indexAwardItemMap.TryGetValue(key, out var existingList) ? - existingList : _indexAwardItemMap[key] = []; + existingList : _indexAwardItemMap[key] = new List(); list.Add(item1.Value); } } @@ -260,7 +260,7 @@ protected override bool ProcessAfterLoad() var key = new Index_SpecialItemKey(item1.Value.Id, item1.Value.Type, indexItem2, indexItem3); { var list = _indexSpecialItemMap.TryGetValue(key, out var existingList) ? - existingList : _indexSpecialItemMap[key] = []; + existingList : _indexSpecialItemMap[key] = new List(); list.Add(item1.Value); } } @@ -271,7 +271,7 @@ protected override bool ProcessAfterLoad() var key = item1.Value.Path?.Dir ?? ""; { var list = _indexItemPathDirMap.TryGetValue(key, out var existingList) ? - existingList : _indexItemPathDirMap[key] = []; + existingList : _indexItemPathDirMap[key] = new List(); list.Add(item1.Value); } } @@ -281,7 +281,7 @@ protected override bool ProcessAfterLoad() { { var list = _indexItemPathNameMap.TryGetValue(item2, out var existingList) ? - existingList : _indexItemPathNameMap[item2] = []; + existingList : _indexItemPathNameMap[item2] = new List(); list.Add(item1.Value); } } @@ -291,7 +291,7 @@ protected override bool ProcessAfterLoad() var key = item1.Value.Path?.Friend?.Id ?? 0; { var list = _indexItemPathFriendIdMap.TryGetValue(key, out var existingList) ? - existingList : _indexItemPathFriendIdMap[key] = []; + existingList : _indexItemPathFriendIdMap[key] = new List(); list.Add(item1.Value); } } @@ -300,7 +300,7 @@ protected override bool ProcessAfterLoad() var key = item1.Value.UseEffect?.Type ?? 0; { var list = _indexUseEffectTypeMap.TryGetValue(key, out var existingList) ? - existingList : _indexUseEffectTypeMap[key] = []; + existingList : _indexUseEffectTypeMap[key] = new List(); list.Add(item1.Value); } } @@ -331,7 +331,7 @@ protected override bool ProcessAfterLoad() var key = item2; { var list = _orderedIndexExtTypeMap.TryGetValue(key, out var existingList) ? - existingList : _orderedIndexExtTypeMap[key] = []; + existingList : _orderedIndexExtTypeMap[key] = new List(); list.Add(item1.Value); } } @@ -345,7 +345,7 @@ protected override bool ProcessAfterLoad() var key = new OrderedIndex_ParamExtTypeKey(indexItem0, indexItem1); { var list = _orderedIndexParamExtTypeMap.TryGetValue(key, out var existingList) ? - existingList : _orderedIndexParamExtTypeMap[key] = []; + existingList : _orderedIndexParamExtTypeMap[key] = new List(); list.Add(item1.Value); } } diff --git a/test/csharp-tableau-loader/tableau/Load.pc.cs b/test/csharp-tableau-loader/tableau/Load.pc.cs index e64ca47c..8be5c1e6 100644 --- a/test/csharp-tableau-loader/tableau/Load.pc.cs +++ b/test/csharp-tableau-loader/tableau/Load.pc.cs @@ -173,11 +173,11 @@ public object Clone() } /// - /// IMessagerName is an interface that provides the static Name() method for messagers. + /// IMessagerName is an interface that provides the Name() method for messagers. /// public interface IMessagerName { - static abstract string Name(); + string Name(); } /// diff --git a/test/csharp-tableau-loader/tableau/PatchConf.pc.cs b/test/csharp-tableau-loader/tableau/PatchConf.pc.cs index 59383a89..e8d99995 100644 --- a/test/csharp-tableau-loader/tableau/PatchConf.pc.cs +++ b/test/csharp-tableau-loader/tableau/PatchConf.pc.cs @@ -19,7 +19,7 @@ public class PatchReplaceConf : Messager, IMessagerName /// /// Name returns the PatchReplaceConf's message name. /// - public static string Name() => Protoconf.PatchReplaceConf.Descriptor.Name; + public string Name() => Protoconf.PatchReplaceConf.Descriptor.Name; /// /// Load loads PatchReplaceConf's content in the given dir, based on format and messager options. @@ -67,7 +67,7 @@ public class PatchMergeConf : Messager, IMessagerName /// /// Name returns the PatchMergeConf's message name. /// - public static string Name() => Protoconf.PatchMergeConf.Descriptor.Name; + public string Name() => Protoconf.PatchMergeConf.Descriptor.Name; /// /// Load loads PatchMergeConf's content in the given dir, based on format and messager options. @@ -122,7 +122,7 @@ public class RecursivePatchConf : Messager, IMessagerName /// /// Name returns the RecursivePatchConf's message name. /// - public static string Name() => Protoconf.RecursivePatchConf.Descriptor.Name; + public string Name() => Protoconf.RecursivePatchConf.Descriptor.Name; /// /// Load loads RecursivePatchConf's content in the given dir, based on format and messager options. diff --git a/test/csharp-tableau-loader/tableau/TestConf.pc.cs b/test/csharp-tableau-loader/tableau/TestConf.pc.cs index 413d6cd7..8819cecf 100644 --- a/test/csharp-tableau-loader/tableau/TestConf.pc.cs +++ b/test/csharp-tableau-loader/tableau/TestConf.pc.cs @@ -17,19 +17,25 @@ public class ActivityConf : Messager, IMessagerName // OrderedMap types. public class OrderedMap_int32Map : SortedDictionary; - public class OrderedMap_protoconf_SectionValue(OrderedMap_int32Map item1, Protoconf.Section item2) - : Tuple(item1, item2); + public class OrderedMap_protoconf_SectionValue : Tuple + { + public OrderedMap_protoconf_SectionValue(OrderedMap_int32Map item1, Protoconf.Section item2) : base(item1, item2) { } + } public class OrderedMap_protoconf_SectionMap : SortedDictionary; - public class OrderedMap_Activity_ChapterValue(OrderedMap_protoconf_SectionMap item1, Protoconf.ActivityConf.Types.Activity.Types.Chapter item2) - : Tuple(item1, item2); + public class OrderedMap_Activity_ChapterValue : Tuple + { + public OrderedMap_Activity_ChapterValue(OrderedMap_protoconf_SectionMap item1, Protoconf.ActivityConf.Types.Activity.Types.Chapter item2) : base(item1, item2) { } + } public class OrderedMap_Activity_ChapterMap : SortedDictionary; - public class OrderedMap_ActivityValue(OrderedMap_Activity_ChapterMap item1, Protoconf.ActivityConf.Types.Activity item2) - : Tuple(item1, item2); + public class OrderedMap_ActivityValue : Tuple + { + public OrderedMap_ActivityValue(OrderedMap_Activity_ChapterMap item1, Protoconf.ActivityConf.Types.Activity item2) : base(item1, item2) { } + } public class OrderedMap_ActivityMap : SortedDictionary; - private OrderedMap_ActivityMap _orderedMap = []; + private OrderedMap_ActivityMap _orderedMap = new OrderedMap_ActivityMap(); // LevelIndex keys. @@ -55,37 +61,37 @@ public override int GetHashCode() => // Index: ActivityName public class Index_ActivityMap : Dictionary>; - private Index_ActivityMap _indexActivityMap = []; + private Index_ActivityMap _indexActivityMap = new Index_ActivityMap(); // Index: ChapterID public class Index_ChapterMap : Dictionary>; - private Index_ChapterMap _indexChapterMap = []; + private Index_ChapterMap _indexChapterMap = new Index_ChapterMap(); - private Dictionary _indexChapterMap1 = []; + private Dictionary _indexChapterMap1 = new Dictionary(); // Index: ChapterName@NamedChapter public class Index_NamedChapterMap : Dictionary>; - private Index_NamedChapterMap _indexNamedChapterMap = []; + private Index_NamedChapterMap _indexNamedChapterMap = new Index_NamedChapterMap(); - private Dictionary _indexNamedChapterMap1 = []; + private Dictionary _indexNamedChapterMap1 = new Dictionary(); // Index: SectionItemID@Award public class Index_AwardMap : Dictionary>; - private Index_AwardMap _indexAwardMap = []; + private Index_AwardMap _indexAwardMap = new Index_AwardMap(); - private Dictionary _indexAwardMap1 = []; + private Dictionary _indexAwardMap1 = new Dictionary(); - private Dictionary _indexAwardMap2 = []; + private Dictionary _indexAwardMap2 = new Dictionary(); private Protoconf.ActivityConf _data = new(); /// /// Name returns the ActivityConf's message name. /// - public static string Name() => Protoconf.ActivityConf.Descriptor.Name; + public string Name() => Protoconf.ActivityConf.Descriptor.Name; /// /// Load loads ActivityConf's content in the given dir, based on format and messager options. @@ -165,7 +171,7 @@ protected override bool ProcessAfterLoad() var key = item1.Value.ActivityName; { var list = _indexActivityMap.TryGetValue(key, out var existingList) ? - existingList : _indexActivityMap[key] = []; + existingList : _indexActivityMap[key] = new List(); list.Add(item1.Value); } } @@ -177,14 +183,14 @@ protected override bool ProcessAfterLoad() var key = item2.Value.ChapterId; { var list = _indexChapterMap.TryGetValue(key, out var existingList) ? - existingList : _indexChapterMap[key] = []; + existingList : _indexChapterMap[key] = new List(); list.Add(item2.Value); } { var map = _indexChapterMap1.TryGetValue(k1, out var existingMap) ? - existingMap : _indexChapterMap1[k1] = []; + existingMap : _indexChapterMap1[k1] = new Index_ChapterMap(); var list = map.TryGetValue(key, out var existingList) ? - existingList : map[key] = []; + existingList : map[key] = new List(); list.Add(item2.Value); } } @@ -193,14 +199,14 @@ protected override bool ProcessAfterLoad() var key = item2.Value.ChapterName; { var list = _indexNamedChapterMap.TryGetValue(key, out var existingList) ? - existingList : _indexNamedChapterMap[key] = []; + existingList : _indexNamedChapterMap[key] = new List(); list.Add(item2.Value); } { var map = _indexNamedChapterMap1.TryGetValue(k1, out var existingMap) ? - existingMap : _indexNamedChapterMap1[k1] = []; + existingMap : _indexNamedChapterMap1[k1] = new Index_NamedChapterMap(); var list = map.TryGetValue(key, out var existingList) ? - existingList : map[key] = []; + existingList : map[key] = new List(); list.Add(item2.Value); } } @@ -213,22 +219,22 @@ protected override bool ProcessAfterLoad() var key = item4.Id; { var list = _indexAwardMap.TryGetValue(key, out var existingList) ? - existingList : _indexAwardMap[key] = []; + existingList : _indexAwardMap[key] = new List(); list.Add(item4); } { var map = _indexAwardMap1.TryGetValue(k1, out var existingMap) ? - existingMap : _indexAwardMap1[k1] = []; + existingMap : _indexAwardMap1[k1] = new Index_AwardMap(); var list = map.TryGetValue(key, out var existingList) ? - existingList : map[key] = []; + existingList : map[key] = new List(); list.Add(item4); } { var mapKey = new LevelIndex_Activity_ChapterKey(k1, k2); var map = _indexAwardMap2.TryGetValue(mapKey, out var existingMap) ? - existingMap : _indexAwardMap2[mapKey] = []; + existingMap : _indexAwardMap2[mapKey] = new Index_AwardMap(); var list = map.TryGetValue(key, out var existingList) ? - existingList : map[key] = []; + existingList : map[key] = new List(); list.Add(item4); } } @@ -491,7 +497,7 @@ public class ChapterConf : Messager, IMessagerName /// /// Name returns the ChapterConf's message name. /// - public static string Name() => Protoconf.ChapterConf.Descriptor.Name; + public string Name() => Protoconf.ChapterConf.Descriptor.Name; /// /// Load loads ChapterConf's content in the given dir, based on format and messager options. @@ -546,7 +552,7 @@ public class ThemeConf : Messager, IMessagerName /// /// Name returns the ThemeConf's message name. /// - public static string Name() => Protoconf.ThemeConf.Descriptor.Name; + public string Name() => Protoconf.ThemeConf.Descriptor.Name; /// /// Load loads ThemeConf's content in the given dir, based on format and messager options. @@ -607,23 +613,23 @@ public class TaskConf : Messager, IMessagerName // Index: ActivityID public class Index_TaskMap : Dictionary>; - private Index_TaskMap _indexTaskMap = []; + private Index_TaskMap _indexTaskMap = new Index_TaskMap(); // OrderedIndex types. // OrderedIndex: Goal@OrderedTask public class OrderedIndex_OrderedTaskMap : SortedDictionary>; - private OrderedIndex_OrderedTaskMap _orderedIndexOrderedTaskMap = []; + private OrderedIndex_OrderedTaskMap _orderedIndexOrderedTaskMap = new OrderedIndex_OrderedTaskMap(); // OrderedIndex: Expiry@TaskExpiry public class OrderedIndex_TaskExpiryMap : SortedDictionary>; - private OrderedIndex_TaskExpiryMap _orderedIndexTaskExpiryMap = []; + private OrderedIndex_TaskExpiryMap _orderedIndexTaskExpiryMap = new OrderedIndex_TaskExpiryMap(); // OrderedIndex: Expiry@SortedTaskExpiry public class OrderedIndex_SortedTaskExpiryMap : SortedDictionary>; - private OrderedIndex_SortedTaskExpiryMap _orderedIndexSortedTaskExpiryMap = []; + private OrderedIndex_SortedTaskExpiryMap _orderedIndexSortedTaskExpiryMap = new OrderedIndex_SortedTaskExpiryMap(); // OrderedIndex: (Expiry,ActivityID)@ActivityExpiry public readonly struct OrderedIndex_ActivityExpiryKey : IComparable @@ -643,14 +649,14 @@ public int CompareTo(OrderedIndex_ActivityExpiryKey other) => public class OrderedIndex_ActivityExpiryMap : SortedDictionary>; - private OrderedIndex_ActivityExpiryMap _orderedIndexActivityExpiryMap = []; + private OrderedIndex_ActivityExpiryMap _orderedIndexActivityExpiryMap = new OrderedIndex_ActivityExpiryMap(); private Protoconf.TaskConf _data = new(); /// /// Name returns the TaskConf's message name. /// - public static string Name() => Protoconf.TaskConf.Descriptor.Name; + public string Name() => Protoconf.TaskConf.Descriptor.Name; /// /// Load loads TaskConf's content in the given dir, based on format and messager options. @@ -701,7 +707,7 @@ protected override bool ProcessAfterLoad() var key = item1.Value.ActivityId; { var list = _indexTaskMap.TryGetValue(key, out var existingList) ? - existingList : _indexTaskMap[key] = []; + existingList : _indexTaskMap[key] = new List(); list.Add(item1.Value); } } @@ -725,7 +731,7 @@ protected override bool ProcessAfterLoad() var key = item1.Value.Goal; { var list = _orderedIndexOrderedTaskMap.TryGetValue(key, out var existingList) ? - existingList : _orderedIndexOrderedTaskMap[key] = []; + existingList : _orderedIndexOrderedTaskMap[key] = new List(); list.Add(item1.Value); } } @@ -734,7 +740,7 @@ protected override bool ProcessAfterLoad() var key = item1.Value.Expiry?.Seconds ?? 0; { var list = _orderedIndexTaskExpiryMap.TryGetValue(key, out var existingList) ? - existingList : _orderedIndexTaskExpiryMap[key] = []; + existingList : _orderedIndexTaskExpiryMap[key] = new List(); list.Add(item1.Value); } } @@ -743,7 +749,7 @@ protected override bool ProcessAfterLoad() var key = item1.Value.Expiry?.Seconds ?? 0; { var list = _orderedIndexSortedTaskExpiryMap.TryGetValue(key, out var existingList) ? - existingList : _orderedIndexSortedTaskExpiryMap[key] = []; + existingList : _orderedIndexSortedTaskExpiryMap[key] = new List(); list.Add(item1.Value); } } @@ -752,7 +758,7 @@ protected override bool ProcessAfterLoad() var key = new OrderedIndex_ActivityExpiryKey(item1.Value.Expiry?.Seconds ?? 0, item1.Value.ActivityId); { var list = _orderedIndexActivityExpiryMap.TryGetValue(key, out var existingList) ? - existingList : _orderedIndexActivityExpiryMap[key] = []; + existingList : _orderedIndexActivityExpiryMap[key] = new List(); list.Add(item1.Value); } } From d6eead29a093ff0ebd7b0d4f4fde3f43eeb881d8 Mon Sep 17 00:00:00 2001 From: wenchy Date: Sun, 15 Mar 2026 23:15:15 +0800 Subject: [PATCH 5/6] test(csharp): implement ActivityConf logic in Program.cs --- test/csharp-tableau-loader/Program.cs | 36 +++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/test/csharp-tableau-loader/Program.cs b/test/csharp-tableau-loader/Program.cs index e539ea70..f0909c8c 100644 --- a/test/csharp-tableau-loader/Program.cs +++ b/test/csharp-tableau-loader/Program.cs @@ -19,6 +19,42 @@ static void Main(string[] _) return; } + var activityConf = hub.GetActivityConf(); + if (activityConf is null) + { + Console.WriteLine("ActivityConf is null"); + } + else + { + // error: not found + var notFound = activityConf.Get3(100001, 1, 999); + if (notFound is null) + { + Console.WriteLine("error: not found: ActivityConf.Get3(100001, 1, 999)"); + } + + // get section + var section = activityConf.Get3(100001, 1, 2); + if (section != null) + { + Console.WriteLine($"ActivityConf.Get3(100001, 1, 2): {section}"); + } + + // OrderedMap traversal + var activityOrderedMap = activityConf.GetOrderedMap(); + foreach (var activityPair in activityOrderedMap) + { + Console.WriteLine($"activityId: {activityPair.Key}"); + Console.WriteLine($" - Activity Data: {activityPair.Value.Item2}"); + var chapterOrderedMap = activityPair.Value.Item1; + foreach (var chapterPair in chapterOrderedMap) + { + Console.WriteLine($" chapterId: {chapterPair.Key}"); + Console.WriteLine($" - Chapter Data: {chapterPair.Value.Item2}"); + } + } + } + var taskConf = hub.Get(); if (taskConf is null) { From 16c12eaaa1593ab29bffb3db8bcccd5141085012 Mon Sep 17 00:00:00 2001 From: wenchy Date: Mon, 16 Mar 2026 00:00:40 +0800 Subject: [PATCH 6/6] fix(csharp): add missing using System.Collections.Generic and System.Linq to generated files --- .../embed/Load.pc.cs | 3 ++ .../embed/Util.pc.cs | 2 ++ .../embed/templates/Hub.pc.cs.tpl | 3 ++ .../indexes/index.go | 2 +- .../indexes/ordered_index.go | 2 +- .../messager.go | 5 +++- .../orderedmap/ordered_map.go | 2 +- test/csharp-tableau-loader/Loader.csproj | 4 ++- test/csharp-tableau-loader/Program.cs | 4 ++- .../tableau/HeroConf.pc.cs | 7 +++-- test/csharp-tableau-loader/tableau/Hub.pc.cs | 3 ++ .../tableau/IndexConf.pc.cs | 25 +++++++++------- .../tableau/ItemConf.pc.cs | 29 ++++++++++--------- test/csharp-tableau-loader/tableau/Load.pc.cs | 3 ++ .../tableau/PatchConf.pc.cs | 3 ++ .../tableau/TestConf.pc.cs | 29 ++++++++++--------- test/csharp-tableau-loader/tableau/Util.pc.cs | 2 ++ 17 files changed, 83 insertions(+), 45 deletions(-) diff --git a/cmd/protoc-gen-csharp-tableau-loader/embed/Load.pc.cs b/cmd/protoc-gen-csharp-tableau-loader/embed/Load.pc.cs index 66a835ec..8990b929 100644 --- a/cmd/protoc-gen-csharp-tableau-loader/embed/Load.pc.cs +++ b/cmd/protoc-gen-csharp-tableau-loader/embed/Load.pc.cs @@ -1,3 +1,6 @@ +using System; +using System.Collections.Generic; +using System.IO; using pb = global::Google.Protobuf; using pbr = global::Google.Protobuf.Reflection; namespace Tableau diff --git a/cmd/protoc-gen-csharp-tableau-loader/embed/Util.pc.cs b/cmd/protoc-gen-csharp-tableau-loader/embed/Util.pc.cs index e720eb81..50644046 100644 --- a/cmd/protoc-gen-csharp-tableau-loader/embed/Util.pc.cs +++ b/cmd/protoc-gen-csharp-tableau-loader/embed/Util.pc.cs @@ -1,3 +1,5 @@ +using System; +using System.IO; namespace Tableau { /// diff --git a/cmd/protoc-gen-csharp-tableau-loader/embed/templates/Hub.pc.cs.tpl b/cmd/protoc-gen-csharp-tableau-loader/embed/templates/Hub.pc.cs.tpl index 3d0013c3..92ddbdfb 100644 --- a/cmd/protoc-gen-csharp-tableau-loader/embed/templates/Hub.pc.cs.tpl +++ b/cmd/protoc-gen-csharp-tableau-loader/embed/templates/Hub.pc.cs.tpl @@ -1,3 +1,6 @@ +using System; +using System.Collections.Generic; +using System.Threading; using pb = global::Google.Protobuf; namespace Tableau { diff --git a/cmd/protoc-gen-csharp-tableau-loader/indexes/index.go b/cmd/protoc-gen-csharp-tableau-loader/indexes/index.go index d9ed850e..d5fb3a7f 100644 --- a/cmd/protoc-gen-csharp-tableau-loader/indexes/index.go +++ b/cmd/protoc-gen-csharp-tableau-loader/indexes/index.go @@ -86,7 +86,7 @@ func (x *Generator) genIndexTypeDef() { x.g.P(helper.Indent(2), "}") x.g.P() } - x.g.P(helper.Indent(2), "public class ", mapType, " : Dictionary<", keyType, ", List<", valueType, ">>;") + x.g.P(helper.Indent(2), "public class ", mapType, " : Dictionary<", keyType, ", List<", valueType, ">> { }") x.g.P() x.g.P(helper.Indent(2), "private ", mapType, " ", x.indexContainerName(index, 0), " = new ", mapType, "();") diff --git a/cmd/protoc-gen-csharp-tableau-loader/indexes/ordered_index.go b/cmd/protoc-gen-csharp-tableau-loader/indexes/ordered_index.go index 3aa40729..d139a283 100644 --- a/cmd/protoc-gen-csharp-tableau-loader/indexes/ordered_index.go +++ b/cmd/protoc-gen-csharp-tableau-loader/indexes/ordered_index.go @@ -83,7 +83,7 @@ func (x *Generator) genOrderedIndexTypeDef() { x.g.P(helper.Indent(2), "}") x.g.P() } - x.g.P(helper.Indent(2), "public class ", mapType, " : SortedDictionary<", keyType, ", List<", valueType, ">>;") + x.g.P(helper.Indent(2), "public class ", mapType, " : SortedDictionary<", keyType, ", List<", valueType, ">> { }") x.g.P() x.g.P(helper.Indent(2), "private ", mapType, " ", x.orderedIndexContainerName(index, 0), " = new ", mapType, "();") diff --git a/cmd/protoc-gen-csharp-tableau-loader/messager.go b/cmd/protoc-gen-csharp-tableau-loader/messager.go index 38135b9f..f38cadcb 100644 --- a/cmd/protoc-gen-csharp-tableau-loader/messager.go +++ b/cmd/protoc-gen-csharp-tableau-loader/messager.go @@ -166,7 +166,10 @@ func genMapGetters(gen *protogen.Plugin, g *protogen.GeneratedFile, md protorefl } } -const staticMessagerContent1 = `using pb = global::Google.Protobuf; +const staticMessagerContent1 = `using System; +using System.Collections.Generic; +using System.Linq; +using pb = global::Google.Protobuf; namespace Tableau {` diff --git a/cmd/protoc-gen-csharp-tableau-loader/orderedmap/ordered_map.go b/cmd/protoc-gen-csharp-tableau-loader/orderedmap/ordered_map.go index 94ad0172..048f7b43 100644 --- a/cmd/protoc-gen-csharp-tableau-loader/orderedmap/ordered_map.go +++ b/cmd/protoc-gen-csharp-tableau-loader/orderedmap/ordered_map.go @@ -75,7 +75,7 @@ func (x *Generator) genOrderedMapTypeDef(md protoreflect.MessageDescriptor, dept x.g.P(helper.Indent(3), "public ", orderedMapValue, "(", nextOrderedMap, " item1, ", currValueType, " item2) : base(item1, item2) { }") x.g.P(helper.Indent(2), "}") } - x.g.P(helper.Indent(2), "public class ", orderedMap, " : SortedDictionary<", keyType, ", ", x.mapValueFieldType(fd), ">;") + x.g.P(helper.Indent(2), "public class ", orderedMap, " : SortedDictionary<", keyType, ", ", x.mapValueFieldType(fd), "> { }") x.g.P() if depth == 1 { x.g.P(helper.Indent(2), "private ", orderedMap, " _orderedMap = new ", orderedMap, "();") diff --git a/test/csharp-tableau-loader/Loader.csproj b/test/csharp-tableau-loader/Loader.csproj index 500449ad..68dbf5e1 100644 --- a/test/csharp-tableau-loader/Loader.csproj +++ b/test/csharp-tableau-loader/Loader.csproj @@ -3,9 +3,11 @@ Exe net8.0 - enable + disable enable CS8981 + + 9 diff --git a/test/csharp-tableau-loader/Program.cs b/test/csharp-tableau-loader/Program.cs index f0909c8c..63f85cdd 100644 --- a/test/csharp-tableau-loader/Program.cs +++ b/test/csharp-tableau-loader/Program.cs @@ -1,4 +1,6 @@ -class Program +using System; + +class Program { static void Main(string[] _) { diff --git a/test/csharp-tableau-loader/tableau/HeroConf.pc.cs b/test/csharp-tableau-loader/tableau/HeroConf.pc.cs index 9fd1f5ca..576ea607 100644 --- a/test/csharp-tableau-loader/tableau/HeroConf.pc.cs +++ b/test/csharp-tableau-loader/tableau/HeroConf.pc.cs @@ -6,6 +6,9 @@ // source: hero_conf.proto // #nullable enable +using System; +using System.Collections.Generic; +using System.Linq; using pb = global::Google.Protobuf; namespace Tableau { @@ -15,13 +18,13 @@ namespace Tableau public class HeroConf : Messager, IMessagerName { // OrderedMap types. - public class OrderedMap_Hero_AttrMap : SortedDictionary; + public class OrderedMap_Hero_AttrMap : SortedDictionary { } public class OrderedMap_HeroValue : Tuple { public OrderedMap_HeroValue(OrderedMap_Hero_AttrMap item1, Protoconf.HeroConf.Types.Hero item2) : base(item1, item2) { } } - public class OrderedMap_HeroMap : SortedDictionary; + public class OrderedMap_HeroMap : SortedDictionary { } private OrderedMap_HeroMap _orderedMap = new OrderedMap_HeroMap(); diff --git a/test/csharp-tableau-loader/tableau/Hub.pc.cs b/test/csharp-tableau-loader/tableau/Hub.pc.cs index 755a36c1..d957fe3f 100644 --- a/test/csharp-tableau-loader/tableau/Hub.pc.cs +++ b/test/csharp-tableau-loader/tableau/Hub.pc.cs @@ -5,6 +5,9 @@ // - protoc v3.19.3 // #nullable enable +using System; +using System.Collections.Generic; +using System.Threading; using pb = global::Google.Protobuf; namespace Tableau { diff --git a/test/csharp-tableau-loader/tableau/IndexConf.pc.cs b/test/csharp-tableau-loader/tableau/IndexConf.pc.cs index 32d5fdcc..e647e830 100644 --- a/test/csharp-tableau-loader/tableau/IndexConf.pc.cs +++ b/test/csharp-tableau-loader/tableau/IndexConf.pc.cs @@ -6,6 +6,9 @@ // source: index_conf.proto // #nullable enable +using System; +using System.Collections.Generic; +using System.Linq; using pb = global::Google.Protobuf; namespace Tableau { @@ -16,7 +19,7 @@ public class FruitConf : Messager, IMessagerName { // OrderedIndex types. // OrderedIndex: Price - public class OrderedIndex_ItemMap : SortedDictionary>; + public class OrderedIndex_ItemMap : SortedDictionary> { } private OrderedIndex_ItemMap _orderedIndexItemMap = new OrderedIndex_ItemMap(); @@ -177,12 +180,12 @@ public class Fruit2Conf : Messager, IMessagerName { // Index types. // Index: CountryName - public class Index_CountryMap : Dictionary>; + public class Index_CountryMap : Dictionary> { } private Index_CountryMap _indexCountryMap = new Index_CountryMap(); // Index: CountryItemAttrName - public class Index_AttrMap : Dictionary>; + public class Index_AttrMap : Dictionary> { } private Index_AttrMap _indexAttrMap = new Index_AttrMap(); @@ -190,7 +193,7 @@ public class Index_AttrMap : Dictionary - public class OrderedIndex_ItemMap : SortedDictionary>; + public class OrderedIndex_ItemMap : SortedDictionary> { } private OrderedIndex_ItemMap _orderedIndexItemMap = new OrderedIndex_ItemMap(); @@ -453,18 +456,18 @@ public class Fruit3Conf : Messager, IMessagerName { // Index types. // Index: CountryName - public class Index_CountryMap : Dictionary>; + public class Index_CountryMap : Dictionary> { } private Index_CountryMap _indexCountryMap = new Index_CountryMap(); // Index: CountryItemAttrName - public class Index_AttrMap : Dictionary>; + public class Index_AttrMap : Dictionary> { } private Index_AttrMap _indexAttrMap = new Index_AttrMap(); // OrderedIndex types. // OrderedIndex: CountryItemPrice - public class OrderedIndex_ItemMap : SortedDictionary>; + public class OrderedIndex_ItemMap : SortedDictionary> { } private OrderedIndex_ItemMap _orderedIndexItemMap = new OrderedIndex_ItemMap(); @@ -669,14 +672,14 @@ public override int GetHashCode() => // Index types. // Index: CountryName - public class Index_CountryMap : Dictionary>; + public class Index_CountryMap : Dictionary> { } private Index_CountryMap _indexCountryMap = new Index_CountryMap(); private Dictionary _indexCountryMap1 = new Dictionary(); // Index: CountryItemAttrName - public class Index_AttrMap : Dictionary>; + public class Index_AttrMap : Dictionary> { } private Index_AttrMap _indexAttrMap = new Index_AttrMap(); @@ -686,7 +689,7 @@ public class Index_AttrMap : Dictionary - public class OrderedIndex_ItemMap : SortedDictionary>; + public class OrderedIndex_ItemMap : SortedDictionary> { } private OrderedIndex_ItemMap _orderedIndexItemMap = new OrderedIndex_ItemMap(); @@ -1066,7 +1069,7 @@ public class Fruit5Conf : Messager, IMessagerName { // Index types. // Index: CountryName - public class Index_CountryMap : Dictionary>; + public class Index_CountryMap : Dictionary> { } private Index_CountryMap _indexCountryMap = new Index_CountryMap(); diff --git a/test/csharp-tableau-loader/tableau/ItemConf.pc.cs b/test/csharp-tableau-loader/tableau/ItemConf.pc.cs index fa73ae8c..19f884ef 100644 --- a/test/csharp-tableau-loader/tableau/ItemConf.pc.cs +++ b/test/csharp-tableau-loader/tableau/ItemConf.pc.cs @@ -6,6 +6,9 @@ // source: item_conf.proto // #nullable enable +using System; +using System.Collections.Generic; +using System.Linq; using pb = global::Google.Protobuf; namespace Tableau { @@ -15,28 +18,28 @@ namespace Tableau public class ItemConf : Messager, IMessagerName { // OrderedMap types. - public class OrderedMap_ItemMap : SortedDictionary; + public class OrderedMap_ItemMap : SortedDictionary { } private OrderedMap_ItemMap _orderedMap = new OrderedMap_ItemMap(); // Index types. // Index: Type - public class Index_ItemMap : Dictionary>; + public class Index_ItemMap : Dictionary> { } private Index_ItemMap _indexItemMap = new Index_ItemMap(); // Index: Param@ItemInfo - public class Index_ItemInfoMap : Dictionary>; + public class Index_ItemInfoMap : Dictionary> { } private Index_ItemInfoMap _indexItemInfoMap = new Index_ItemInfoMap(); // Index: Default@ItemDefaultInfo - public class Index_ItemDefaultInfoMap : Dictionary>; + public class Index_ItemDefaultInfoMap : Dictionary> { } private Index_ItemDefaultInfoMap _indexItemDefaultInfoMap = new Index_ItemDefaultInfoMap(); // Index: ExtType@ItemExtInfo - public class Index_ItemExtInfoMap : Dictionary>; + public class Index_ItemExtInfoMap : Dictionary> { } private Index_ItemExtInfoMap _indexItemExtInfoMap = new Index_ItemExtInfoMap(); @@ -59,7 +62,7 @@ public override int GetHashCode() => (Id, Name).GetHashCode(); } - public class Index_AwardItemMap : Dictionary>; + public class Index_AwardItemMap : Dictionary> { } private Index_AwardItemMap _indexAwardItemMap = new Index_AwardItemMap(); @@ -86,33 +89,33 @@ public override int GetHashCode() => (Id, Type, Param, ExtType).GetHashCode(); } - public class Index_SpecialItemMap : Dictionary>; + public class Index_SpecialItemMap : Dictionary> { } private Index_SpecialItemMap _indexSpecialItemMap = new Index_SpecialItemMap(); // Index: PathDir@ItemPathDir - public class Index_ItemPathDirMap : Dictionary>; + public class Index_ItemPathDirMap : Dictionary> { } private Index_ItemPathDirMap _indexItemPathDirMap = new Index_ItemPathDirMap(); // Index: PathName@ItemPathName - public class Index_ItemPathNameMap : Dictionary>; + public class Index_ItemPathNameMap : Dictionary> { } private Index_ItemPathNameMap _indexItemPathNameMap = new Index_ItemPathNameMap(); // Index: PathFriendID@ItemPathFriendID - public class Index_ItemPathFriendIDMap : Dictionary>; + public class Index_ItemPathFriendIDMap : Dictionary> { } private Index_ItemPathFriendIDMap _indexItemPathFriendIdMap = new Index_ItemPathFriendIDMap(); // Index: UseEffectType@UseEffectType - public class Index_UseEffectTypeMap : Dictionary>; + public class Index_UseEffectTypeMap : Dictionary> { } private Index_UseEffectTypeMap _indexUseEffectTypeMap = new Index_UseEffectTypeMap(); // OrderedIndex types. // OrderedIndex: ExtType@ExtType - public class OrderedIndex_ExtTypeMap : SortedDictionary>; + public class OrderedIndex_ExtTypeMap : SortedDictionary> { } private OrderedIndex_ExtTypeMap _orderedIndexExtTypeMap = new OrderedIndex_ExtTypeMap(); @@ -132,7 +135,7 @@ public int CompareTo(OrderedIndex_ParamExtTypeKey other) => (Param, ExtType).CompareTo((other.Param, other.ExtType)); } - public class OrderedIndex_ParamExtTypeMap : SortedDictionary>; + public class OrderedIndex_ParamExtTypeMap : SortedDictionary> { } private OrderedIndex_ParamExtTypeMap _orderedIndexParamExtTypeMap = new OrderedIndex_ParamExtTypeMap(); diff --git a/test/csharp-tableau-loader/tableau/Load.pc.cs b/test/csharp-tableau-loader/tableau/Load.pc.cs index 8be5c1e6..463f9aa8 100644 --- a/test/csharp-tableau-loader/tableau/Load.pc.cs +++ b/test/csharp-tableau-loader/tableau/Load.pc.cs @@ -5,6 +5,9 @@ // - protoc v3.19.3 // #nullable enable +using System; +using System.Collections.Generic; +using System.IO; using pb = global::Google.Protobuf; using pbr = global::Google.Protobuf.Reflection; namespace Tableau diff --git a/test/csharp-tableau-loader/tableau/PatchConf.pc.cs b/test/csharp-tableau-loader/tableau/PatchConf.pc.cs index e8d99995..6290448b 100644 --- a/test/csharp-tableau-loader/tableau/PatchConf.pc.cs +++ b/test/csharp-tableau-loader/tableau/PatchConf.pc.cs @@ -6,6 +6,9 @@ // source: patch_conf.proto // #nullable enable +using System; +using System.Collections.Generic; +using System.Linq; using pb = global::Google.Protobuf; namespace Tableau { diff --git a/test/csharp-tableau-loader/tableau/TestConf.pc.cs b/test/csharp-tableau-loader/tableau/TestConf.pc.cs index 8819cecf..51404588 100644 --- a/test/csharp-tableau-loader/tableau/TestConf.pc.cs +++ b/test/csharp-tableau-loader/tableau/TestConf.pc.cs @@ -6,6 +6,9 @@ // source: test_conf.proto // #nullable enable +using System; +using System.Collections.Generic; +using System.Linq; using pb = global::Google.Protobuf; namespace Tableau { @@ -15,25 +18,25 @@ namespace Tableau public class ActivityConf : Messager, IMessagerName { // OrderedMap types. - public class OrderedMap_int32Map : SortedDictionary; + public class OrderedMap_int32Map : SortedDictionary { } public class OrderedMap_protoconf_SectionValue : Tuple { public OrderedMap_protoconf_SectionValue(OrderedMap_int32Map item1, Protoconf.Section item2) : base(item1, item2) { } } - public class OrderedMap_protoconf_SectionMap : SortedDictionary; + public class OrderedMap_protoconf_SectionMap : SortedDictionary { } public class OrderedMap_Activity_ChapterValue : Tuple { public OrderedMap_Activity_ChapterValue(OrderedMap_protoconf_SectionMap item1, Protoconf.ActivityConf.Types.Activity.Types.Chapter item2) : base(item1, item2) { } } - public class OrderedMap_Activity_ChapterMap : SortedDictionary; + public class OrderedMap_Activity_ChapterMap : SortedDictionary { } public class OrderedMap_ActivityValue : Tuple { public OrderedMap_ActivityValue(OrderedMap_Activity_ChapterMap item1, Protoconf.ActivityConf.Types.Activity item2) : base(item1, item2) { } } - public class OrderedMap_ActivityMap : SortedDictionary; + public class OrderedMap_ActivityMap : SortedDictionary { } private OrderedMap_ActivityMap _orderedMap = new OrderedMap_ActivityMap(); @@ -59,26 +62,26 @@ public override int GetHashCode() => // Index types. // Index: ActivityName - public class Index_ActivityMap : Dictionary>; + public class Index_ActivityMap : Dictionary> { } private Index_ActivityMap _indexActivityMap = new Index_ActivityMap(); // Index: ChapterID - public class Index_ChapterMap : Dictionary>; + public class Index_ChapterMap : Dictionary> { } private Index_ChapterMap _indexChapterMap = new Index_ChapterMap(); private Dictionary _indexChapterMap1 = new Dictionary(); // Index: ChapterName@NamedChapter - public class Index_NamedChapterMap : Dictionary>; + public class Index_NamedChapterMap : Dictionary> { } private Index_NamedChapterMap _indexNamedChapterMap = new Index_NamedChapterMap(); private Dictionary _indexNamedChapterMap1 = new Dictionary(); // Index: SectionItemID@Award - public class Index_AwardMap : Dictionary>; + public class Index_AwardMap : Dictionary> { } private Index_AwardMap _indexAwardMap = new Index_AwardMap(); @@ -611,23 +614,23 @@ public class TaskConf : Messager, IMessagerName { // Index types. // Index: ActivityID - public class Index_TaskMap : Dictionary>; + public class Index_TaskMap : Dictionary> { } private Index_TaskMap _indexTaskMap = new Index_TaskMap(); // OrderedIndex types. // OrderedIndex: Goal@OrderedTask - public class OrderedIndex_OrderedTaskMap : SortedDictionary>; + public class OrderedIndex_OrderedTaskMap : SortedDictionary> { } private OrderedIndex_OrderedTaskMap _orderedIndexOrderedTaskMap = new OrderedIndex_OrderedTaskMap(); // OrderedIndex: Expiry@TaskExpiry - public class OrderedIndex_TaskExpiryMap : SortedDictionary>; + public class OrderedIndex_TaskExpiryMap : SortedDictionary> { } private OrderedIndex_TaskExpiryMap _orderedIndexTaskExpiryMap = new OrderedIndex_TaskExpiryMap(); // OrderedIndex: Expiry@SortedTaskExpiry - public class OrderedIndex_SortedTaskExpiryMap : SortedDictionary>; + public class OrderedIndex_SortedTaskExpiryMap : SortedDictionary> { } private OrderedIndex_SortedTaskExpiryMap _orderedIndexSortedTaskExpiryMap = new OrderedIndex_SortedTaskExpiryMap(); @@ -647,7 +650,7 @@ public int CompareTo(OrderedIndex_ActivityExpiryKey other) => (Expiry, ActivityId).CompareTo((other.Expiry, other.ActivityId)); } - public class OrderedIndex_ActivityExpiryMap : SortedDictionary>; + public class OrderedIndex_ActivityExpiryMap : SortedDictionary> { } private OrderedIndex_ActivityExpiryMap _orderedIndexActivityExpiryMap = new OrderedIndex_ActivityExpiryMap(); diff --git a/test/csharp-tableau-loader/tableau/Util.pc.cs b/test/csharp-tableau-loader/tableau/Util.pc.cs index d522550f..c907729d 100644 --- a/test/csharp-tableau-loader/tableau/Util.pc.cs +++ b/test/csharp-tableau-loader/tableau/Util.pc.cs @@ -5,6 +5,8 @@ // - protoc v3.19.3 // #nullable enable +using System; +using System.IO; namespace Tableau { ///