diff --git a/.gitignore b/.gitignore index f686f554..fed72e45 100644 --- a/.gitignore +++ b/.gitignore @@ -39,6 +39,10 @@ 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 +!test/testdata/bin/ +test/csharp-tableau-loader/protoconf + diff --git a/README.md b/README.md index 44cde351..5f085cf2 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,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..83626417 --- /dev/null +++ b/cmd/protoc-gen-csharp-tableau-loader/embed/Load.pc.cs @@ -0,0 +1,112 @@ +using pb = global::Google.Protobuf; +using pbr = global::Google.Protobuf.Reflection; +namespace Tableau +{ + public static class Load + { + public delegate byte[] ReadFunc(string path); + public delegate pb::IMessage? LoadFunc(pbr::MessageDescriptor desc, string dir, Format fmt, in MessagerOptions? options); + + public class BaseOptions + { + public bool? IgnoreUnknownFields { get; set; } + public ReadFunc? ReadFunc { get; set; } + public LoadFunc? LoadFunc { get; set; } + } + + public class Options : BaseOptions + { + public Dictionary? MessagerOptions { get; set; } + 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; + } + } + + public class MessagerOptions : BaseOptions, ICloneable + { + public string? Path { get; set; } + + public object Clone() + { + return new MessagerOptions + { + IgnoreUnknownFields = IgnoreUnknownFields, + ReadFunc = ReadFunc, + LoadFunc = LoadFunc, + Path = Path + }; + } + } + + public static pb::IMessage? LoadMessager(pbr::MessageDescriptor desc, string path, Format fmt, in MessagerOptions? options = null) + { + var readFunc = options?.ReadFunc ?? File.ReadAllBytes; + byte[] content = readFunc(path); + return Unmarshal(content, desc, fmt, options); + } + + 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); + } + + public static pb::IMessage? Unmarshal(byte[] content, pbr::MessageDescriptor desc, Format fmt, in MessagerOptions? options = null) + { + switch (fmt) + { + case Format.JSON: + var parser = new pb::JsonParser( + pb::JsonParser.Settings.Default.WithIgnoreUnknownFields(options?.IgnoreUnknownFields ?? false) + ); + return parser.Parse(new StreamReader(new MemoryStream(content)), desc); + case Format.Bin: + return desc.Parser.ParseFrom(content); + default: + return null; + } + } + } + + public interface IMessagerName + { + static abstract string Name(); + } + + public abstract class Messager + { + public class Stats + { + public TimeSpan Duration; + } + + protected Stats LoadStats = new(); + + public ref readonly Stats GetStats() => ref LoadStats; + + public abstract bool Load(string dir, Format fmt, in Load.MessagerOptions? options = null); + + public virtual pb::IMessage? Message() => null; + + protected virtual bool ProcessAfterLoad() => true; + + public virtual bool ProcessAfterLoadAll(in Hub hub) => true; + } +} 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..daabb389 --- /dev/null +++ b/cmd/protoc-gen-csharp-tableau-loader/embed/Util.pc.cs @@ -0,0 +1,38 @@ +namespace Tableau +{ + public enum Format + { + Unknown, + JSON, + Bin + } + + public static class Util + { + + private const string _unknownExt = ".unknown"; + private const string _jsonExt = ".json"; + private const string _binExt = ".binpb"; + + public static Format GetFormat(string path) + { + string ext = Path.GetExtension(path); + return ext switch + { + _jsonExt => Format.JSON, + _binExt => Format.Bin, + _ => Format.Unknown, + }; + } + + 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..6ef85189 --- /dev/null +++ b/cmd/protoc-gen-csharp-tableau-loader/embed/templates/Hub.pc.cs.tpl @@ -0,0 +1,97 @@ +using pb = global::Google.Protobuf; +namespace Tableau +{ + internal class MessagerContainer(in Dictionary? messagerMap = null) + { + public Dictionary MessagerMap = messagerMap ?? []; + public DateTime LastLoadedTime = DateTime.Now; +{{ range . }} public {{ . }}? {{ . }} = InternalGet<{{ . }}>(messagerMap); +{{ end }} + 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; + } + + 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); + } + } + + public class HubOptions + { + public Func? Filter { get; set; } + } + + public class Hub(HubOptions? options = null) + { + private readonly Atomic _messagerContainer = new(); + private readonly HubOptions? _options = options; + + 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))) + { + return false; + } + } + var tmpHub = new Hub(); + tmpHub.SetMessagerMap(messagerMap); + foreach (var messager in messagerMap.Values) + { + if (!messager.ProcessAfterLoadAll(tmpHub)) + { + return false; + } + } + SetMessagerMap(messagerMap); + return true; + } + + public IReadOnlyDictionary? GetMessagerMap() => _messagerContainer.Value?.MessagerMap; + + public void SetMessagerMap(in Dictionary map) => _messagerContainer.Value = new MessagerContainer(map); + + public T? Get() where T : Messager, IMessagerName => _messagerContainer.Value?.Get(); +{{ range . }} + public {{ . }}? Get{{ . }}() => _messagerContainer.Value?.{{ . }}; +{{ end }} + public DateTime? GetLastLoadedTime() => _messagerContainer.Value?.LastLoadedTime; + + 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; + } + } + + public class Registry + { + internal static readonly Dictionary> Registrar = []; + + public static void Register() where T : Messager, IMessagerName, new() => Registrar[T.Name()] = () => new T(); + + 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..19f15b1b --- /dev/null +++ b/cmd/protoc-gen-csharp-tableau-loader/helper/helper.go @@ -0,0 +1,244 @@ +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 strcase.ToCamel(string(fd.Name())) +} + +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()) + } + return ParseIndexFieldName(fd) +} + +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 { + fullname := string(md.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, ".") +} + +// 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 +} + +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..337b4245 --- /dev/null +++ b/cmd/protoc-gen-csharp-tableau-loader/indexes/generator.go @@ -0,0 +1,135 @@ +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 + maxDepth int + keys helper.MapKeySlice + mapFds []protoreflect.FieldDescriptor +} + +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 levelMessage := x.descriptor.LevelMessage; levelMessage != nil; levelMessage = levelMessage.NextLevel { + if fd := levelMessage.FD; fd != nil && fd.IsMap() { + x.keys = x.keys.AddMapKey(helper.MapKey{ + Type: helper.ParseMapKeyType(fd.MapKey()), + Name: helper.ParseMapFieldNameAsFuncParam(fd), + FieldName: helper.ParseMapFieldNameAsKeyStructFieldName(fd), + }) + x.mapFds = append(x.mapFds, fd) + } + if len(levelMessage.Indexes) != 0 || len(levelMessage.OrderedIndexes) != 0 { + x.maxDepth = levelMessage.MapDepth + } + } +} + +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 + } + for i := 1; i <= x.maxDepth-3 && i <= len(x.mapFds)-1; i++ { + if i == 1 { + x.g.P() + x.g.P(helper.Indent(2), "// LevelIndex keys.") + } + fd := x.mapFds[i] + 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+1] + 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..740131c8 --- /dev/null +++ b/cmd/protoc-gen-csharp-tableau-loader/indexes/index.go @@ -0,0 +1,331 @@ +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/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 levelMessage := x.descriptor.LevelMessage; levelMessage != nil; levelMessage = levelMessage.NextLevel { + for _, index := range levelMessage.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 <= levelMessage.MapDepth-2; i++ { + if i > len(x.keys) { + break + } + 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.mapFds[i-1]) + 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 levelMessage := x.descriptor.LevelMessage; levelMessage != nil; levelMessage = levelMessage.NextLevel { + for _, index := range levelMessage.Indexes { + x.g.P(helper.Indent(3), x.indexContainerName(index, 0), ".Clear();") + for i := 1; i <= levelMessage.MapDepth-2; i++ { + if i > len(x.keys) { + break + } + x.g.P(helper.Indent(3), x.indexContainerName(index, i), ".Clear();") + } + } + } + parentDataName := "_data" + for levelMessage := x.descriptor.LevelMessage; levelMessage != nil; levelMessage = levelMessage.NextLevel { + for _, index := range levelMessage.Indexes { + x.genOneIndexLoader(levelMessage.MapDepth, levelMessage.Depth, index, parentDataName) + } + itemName := fmt.Sprintf("item%d", levelMessage.Depth) + if levelMessage.FD == nil { + break + } + if !levelMessage.NextLevel.NeedGenIndex() { + break + } + x.g.P(helper.Indent(levelMessage.Depth+2), "foreach (var ", itemName, " in ", parentDataName, x.fieldGetter(levelMessage.FD), ")") + x.g.P(helper.Indent(levelMessage.Depth+2), "{") + parentDataName = itemName + if levelMessage.FD.IsMap() { + x.g.P(helper.Indent(levelMessage.Depth+3), "var k", levelMessage.MapDepth, " = ", itemName, ".Key;") + parentDataName = itemName + ".Value" + } + defer x.g.P(helper.Indent(levelMessage.Depth+2), "}") + } +} + +func (x *Generator) genOneIndexLoader(depth int, ident int, index *index.LevelIndex, parentDataName string) { + 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", depth) + x.g.P(helper.Indent(ident+3), "foreach (var ", itemName, " in ", parentDataName, fieldName, ")") + x.g.P(helper.Indent(ident+3), "{") + key := itemName + x.genLoader(depth, ident+4, index, 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(depth, ident+3, index, "key", parentDataName) + } + } else { + // multi-column index + x.generateOneMulticolumnIndex(depth, ident+2, index, parentDataName, nil) + } + x.g.P(helper.Indent(ident+2), "}") +} + +func (x *Generator) generateOneMulticolumnIndex(depth, ident int, index *index.LevelIndex, 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(depth, ident+1, index, "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(depth, ident+1, index, parentDataName, keys) + x.g.P(helper.Indent(ident+1), "}") + } else { + key := parentDataName + fieldName + keys = keys.AddMapKey(helper.MapKey{Name: key}) + x.generateOneMulticolumnIndex(depth, ident, index, parentDataName, keys) + } +} + +func (x *Generator) genLoader(depth, ident int, index *index.LevelIndex, 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 <= depth-2; i++ { + if i > len(x.keys) { + break + } + 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.mapFds[i-1]) + 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 levelMessage := x.descriptor.LevelMessage; levelMessage != nil; levelMessage = levelMessage.NextLevel { + for _, index := range levelMessage.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), "}") + for i := 1; i <= levelMessage.MapDepth-2; i++ { + if i > len(x.keys) { + break + } + 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 levelMessage := x.descriptor.LevelMessage; levelMessage != nil; levelMessage = levelMessage.NextLevel { + for _, index := range levelMessage.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(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), "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), "public ", mapValueType, "? FindFirst", index.Name(), "(", params, ") =>") + x.g.P(helper.Indent(3), "Find", index.Name(), "(", args, ")?.FirstOrDefault();") + + for i := 1; i <= levelMessage.MapDepth-2; i++ { + if i > len(x.keys) { + break + } + indexContainerName := x.indexContainerName(index, i) + partKeys := x.keys[:i] + partParams := partKeys.GenGetParams() + partArgs := partKeys.GenGetArguments() + x.g.P() + 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.mapFds[i-1]) + 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), "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), "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..b2a35748 --- /dev/null +++ b/cmd/protoc-gen-csharp-tableau-loader/indexes/ordered_index.go @@ -0,0 +1,328 @@ +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/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 levelMessage := x.descriptor.LevelMessage; levelMessage != nil; levelMessage = levelMessage.NextLevel { + for _, index := range levelMessage.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 <= levelMessage.MapDepth-2; i++ { + if i > len(x.keys) { + break + } + 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.mapFds[i-1]) + 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 levelMessage := x.descriptor.LevelMessage; levelMessage != nil; levelMessage = levelMessage.NextLevel { + for _, index := range levelMessage.OrderedIndexes { + x.g.P(helper.Indent(3), x.orderedIndexContainerName(index, 0), ".Clear();") + for i := 1; i <= levelMessage.MapDepth-2; i++ { + if i > len(x.keys) { + break + } + x.g.P(helper.Indent(3), x.orderedIndexContainerName(index, i), ".Clear();") + } + } + } + parentDataName := "_data" + for levelMessage := x.descriptor.LevelMessage; levelMessage != nil; levelMessage = levelMessage.NextLevel { + for _, index := range levelMessage.OrderedIndexes { + x.genOneOrderedIndexLoader(levelMessage.MapDepth, levelMessage.Depth, index, parentDataName) + } + itemName := fmt.Sprintf("item%d", levelMessage.Depth) + if levelMessage.FD == nil { + break + } + if !levelMessage.NextLevel.NeedGenOrderedIndex() { + break + } + x.g.P(helper.Indent(levelMessage.Depth+2), "foreach (var ", itemName, " in ", parentDataName, x.fieldGetter(levelMessage.FD), ")") + x.g.P(helper.Indent(levelMessage.Depth+2), "{") + parentDataName = itemName + if levelMessage.FD.IsMap() { + x.g.P(helper.Indent(levelMessage.Depth+3), "var k", levelMessage.MapDepth, " = ", itemName, ".Key;") + parentDataName = itemName + ".Value" + } + defer x.g.P(helper.Indent(levelMessage.Depth+2), "}") + } +} + +func (x *Generator) genOneOrderedIndexLoader(depth int, ident int, index *index.LevelIndex, parentDataName string) { + 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", depth) + 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(depth, ident+4, index, "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(depth, ident+3, index, "key", parentDataName) + } + } else { + // multi-column index + x.generateOneMulticolumnOrderedIndex(depth, ident+2, index, parentDataName, nil) + } + x.g.P(helper.Indent(ident+2), "}") +} + +func (x *Generator) generateOneMulticolumnOrderedIndex(depth, ident int, index *index.LevelIndex, 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(depth, ident+1, index, "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(depth, ident+1, index, parentDataName, keys) + x.g.P(helper.Indent(ident+1), "}") + } else { + key := parentDataName + fieldName + suffix + keys = keys.AddMapKey(helper.MapKey{Name: key}) + x.generateOneMulticolumnOrderedIndex(depth, ident, index, parentDataName, keys) + } +} + +func (x *Generator) genOrderedIndexLoaderCommon(depth, ident int, index *index.LevelIndex, 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 <= depth-2; i++ { + if i > len(x.keys) { + break + } + 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.mapFds[i-1]) + 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 levelMessage := x.descriptor.LevelMessage; levelMessage != nil; levelMessage = levelMessage.NextLevel { + for _, index := range levelMessage.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), "}") + for i := 1; i <= levelMessage.MapDepth-2; i++ { + if i > len(x.keys) { + break + } + 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 levelMessage := x.descriptor.LevelMessage; levelMessage != nil; levelMessage = levelMessage.NextLevel { + for _, index := range levelMessage.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(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), "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), "public ", mapValueType, "? FindFirst", index.Name(), "(", params, ") =>") + x.g.P(helper.Indent(3), "Find", index.Name(), "(", args, ")?.FirstOrDefault();") + + for i := 1; i <= levelMessage.MapDepth-2; i++ { + if i > len(x.keys) { + break + } + indexContainerName := x.orderedIndexContainerName(index, i) + partKeys := x.keys[:i] + partParams := partKeys.GenGetParams() + partArgs := partKeys.GenGetArguments() + x.g.P() + 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.mapFds[i-1]) + 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), "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), "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..78d1a2c5 --- /dev/null +++ b/cmd/protoc-gen-csharp-tableau-loader/messager.go @@ -0,0 +1,146 @@ +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/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), "public class ", messagerName, " : Messager, IMessagerName") + g.P(helper.Indent(1), "{") + // type definitions + orderedMapGenerator.GenOrderedMapTypeDef() + indexGenerator.GenIndexTypeDef() + g.P(helper.Indent(2), "private Protoconf.", messagerName, " _data = new();") + g.P() + g.P(helper.Indent(2), "public static string Name() => Protoconf.", messagerName, ".Descriptor.Name;") + g.P() + 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 = (Protoconf.", messagerName, ")(") + g.P(helper.Indent(5), "Tableau.Load.LoadMessagerInDir(Protoconf.", messagerName, ".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)") + g.P(helper.Indent(3), "{") + 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), "public ref readonly Protoconf.", messagerName, " Data() => ref _data;") + g.P() + g.P(helper.Indent(2), "public override pb::IMessage? Message() => _data;") + + if orderedMapGenerator.NeedGenerate() || indexGenerator.NeedGenerate() { + g.P() + 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() + + 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.", strcase.ToCamel(string(fd.Name())), "?.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(), ")?.", strcase.ToCamel(string(fd.Name())), "?.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..cf0d4300 --- /dev/null +++ b/cmd/protoc-gen-csharp-tableau-loader/orderedmap/ordered_map.go @@ -0,0 +1,194 @@ +package orderedmap + +import ( + "fmt" + + "github.com/iancoleman/strcase" + "github.com/tableauio/loader/cmd/protoc-gen-csharp-tableau-loader/helper" + "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.g.P(helper.Indent(2), "// OrderedMap types.") + 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() { + 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() + } + return + } + } +} + +func (x *Generator) GenOrderedMapLoader() { + if !x.NeedGenerate() { + return + } + x.g.P(helper.Indent(3), "// OrderedMap init.") + x.g.P(helper.Indent(3), "_orderedMap.Clear();") + 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() { + 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, ".", strcase.ToCamel(string(fd.Name())), ")") + 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), "public ref readonly ", orderedMap, " ", getter, "() => ref _orderedMap;") + } else { + 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/go.mod b/go.mod index 18fc232f..7154049d 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/stretchr/testify v1.10.0 github.com/tableauio/tableau v0.14.2 golang.org/x/exp v0.0.0-20230418202329-0354be287a23 + golang.org/x/text v0.19.0 google.golang.org/protobuf v1.34.2 ) @@ -40,7 +41,6 @@ require ( golang.org/x/crypto v0.28.0 // indirect golang.org/x/net v0.30.0 // indirect golang.org/x/sync v0.8.0 // indirect - golang.org/x/text v0.19.0 // indirect google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect 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..4fd55614 --- /dev/null +++ b/test/csharp-tableau-loader/gen.bat @@ -0,0 +1,56 @@ +@echo off +setlocal +setlocal enabledelayedexpansion + +for /f "delims=" %%i in ('git rev-parse --show-toplevel') do set repoRoot=%%i +cd /d "%repoRoot%" + +set "PROTOC=%repoRoot%\third_party\_submodules\protobuf\cmake\build\protoc.exe" +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..cc9c5e14 --- /dev/null +++ b/test/csharp-tableau-loader/gen.sh @@ -0,0 +1,48 @@ +#!/bin/bash + +# set -eux +set -e +set -o pipefail + +shopt -s globstar + +cd "$(git rev-parse --show-toplevel)" +PROTOC="./third_party/_submodules/protobuf/src/protoc" +PROTOBUF_PROTO="./third_party/_submodules/protobuf/src" +TABLEAU_PROTO="./third_party/_submodules/tableau/proto" +ROOTDIR="./test/csharp-tableau-loader" +PLGUIN_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 "${PLGUIN_DIR}" && go build && cd - + +export PATH="${PATH}:${PLGUIN_DIR}" + +${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" \ + "$PROTOCONF_IN"/**/*.proto + +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..80a048a2 --- /dev/null +++ b/test/csharp-tableau-loader/tableau/HeroConf.pc.cs @@ -0,0 +1,112 @@ +// +// 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 +{ + 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(); + + public static string Name() => Protoconf.HeroConf.Descriptor.Name; + + 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) + { + return false; + } + LoadStats.Duration = DateTime.Now - start; + return ProcessAfterLoad(); + } + + public ref readonly Protoconf.HeroConf Data() => ref _data; + + public override pb::IMessage? Message() => _data; + + 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; + } + + public Protoconf.HeroConf.Types.Hero? Get1(string name) => + _data.HeroMap?.TryGetValue(name, out var val) == true ? val : null; + + 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. + public ref readonly OrderedMap_HeroMap GetOrderedMap() => ref _orderedMap; + + public OrderedMap_Hero_AttrMap? GetOrderedMap1(string name) => + _orderedMap.TryGetValue(name, out var value) ? value.Item1 : null; + } + + public class HeroBaseConf : Messager, IMessagerName + { + private Protoconf.HeroBaseConf _data = new(); + + public static string Name() => Protoconf.HeroBaseConf.Descriptor.Name; + + 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) + { + return false; + } + LoadStats.Duration = DateTime.Now - start; + return ProcessAfterLoad(); + } + + public ref readonly Protoconf.HeroBaseConf Data() => ref _data; + + public override pb::IMessage? Message() => _data; + + public Base.Hero? Get1(string name) => + _data.HeroMap?.TryGetValue(name, out var val) == true ? val : null; + + 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..87149166 --- /dev/null +++ b/test/csharp-tableau-loader/tableau/Hub.pc.cs @@ -0,0 +1,152 @@ +// +// 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 +{ + internal class MessagerContainer(in Dictionary? messagerMap = null) + { + public Dictionary MessagerMap = messagerMap ?? []; + public DateTime LastLoadedTime = DateTime.Now; + public HeroConf? HeroConf = InternalGet(messagerMap); + public HeroBaseConf? HeroBaseConf = InternalGet(messagerMap); + public FruitConf? FruitConf = InternalGet(messagerMap); + public Fruit2Conf? Fruit2Conf = InternalGet(messagerMap); + public Fruit3Conf? Fruit3Conf = InternalGet(messagerMap); + public ItemConf? ItemConf = InternalGet(messagerMap); + public PatchReplaceConf? PatchReplaceConf = InternalGet(messagerMap); + public PatchMergeConf? PatchMergeConf = InternalGet(messagerMap); + public RecursivePatchConf? RecursivePatchConf = InternalGet(messagerMap); + public ActivityConf? ActivityConf = InternalGet(messagerMap); + public ChapterConf? ChapterConf = InternalGet(messagerMap); + public ThemeConf? ThemeConf = InternalGet(messagerMap); + public TaskConf? TaskConf = InternalGet(messagerMap); + + 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; + } + + 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); + } + } + + public class HubOptions + { + public Func? Filter { get; set; } + } + + public class Hub(HubOptions? options = null) + { + private readonly Atomic _messagerContainer = new(); + private readonly HubOptions? _options = options; + + 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))) + { + return false; + } + } + var tmpHub = new Hub(); + tmpHub.SetMessagerMap(messagerMap); + foreach (var messager in messagerMap.Values) + { + if (!messager.ProcessAfterLoadAll(tmpHub)) + { + return false; + } + } + SetMessagerMap(messagerMap); + return true; + } + + public IReadOnlyDictionary? GetMessagerMap() => _messagerContainer.Value?.MessagerMap; + + public void SetMessagerMap(in Dictionary map) => _messagerContainer.Value = new MessagerContainer(map); + + public T? Get() where T : Messager, IMessagerName => _messagerContainer.Value?.Get(); + + public HeroConf? GetHeroConf() => _messagerContainer.Value?.HeroConf; + + public HeroBaseConf? GetHeroBaseConf() => _messagerContainer.Value?.HeroBaseConf; + + public FruitConf? GetFruitConf() => _messagerContainer.Value?.FruitConf; + + public Fruit2Conf? GetFruit2Conf() => _messagerContainer.Value?.Fruit2Conf; + + public Fruit3Conf? GetFruit3Conf() => _messagerContainer.Value?.Fruit3Conf; + + public ItemConf? GetItemConf() => _messagerContainer.Value?.ItemConf; + + public PatchReplaceConf? GetPatchReplaceConf() => _messagerContainer.Value?.PatchReplaceConf; + + public PatchMergeConf? GetPatchMergeConf() => _messagerContainer.Value?.PatchMergeConf; + + public RecursivePatchConf? GetRecursivePatchConf() => _messagerContainer.Value?.RecursivePatchConf; + + 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 DateTime? GetLastLoadedTime() => _messagerContainer.Value?.LastLoadedTime; + + 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; + } + } + + public class Registry + { + internal static readonly Dictionary> Registrar = []; + + public static void Register() where T : Messager, IMessagerName, new() => Registrar[T.Name()] = () => new T(); + + public static void Init() + { + 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..16ac4e6c --- /dev/null +++ b/test/csharp-tableau-loader/tableau/IndexConf.pc.cs @@ -0,0 +1,448 @@ +// +// 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 +{ + 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(); + + public static string Name() => Protoconf.FruitConf.Descriptor.Name; + + 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) + { + return false; + } + LoadStats.Duration = DateTime.Now - start; + return ProcessAfterLoad(); + } + + public ref readonly Protoconf.FruitConf Data() => ref _data; + + public override pb::IMessage? Message() => _data; + + 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) + { + var k2 = item2.Key; + { + // 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; + } + + public Protoconf.FruitConf.Types.Fruit? Get1(int fruitType) => + _data.FruitMap?.TryGetValue(fruitType, out var val) == true ? val : null; + + 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 + public ref readonly OrderedIndex_ItemMap FindItemMap() => ref _orderedIndexItemMap; + + public List? FindItem(int price) => + _orderedIndexItemMap.TryGetValue(price, out var value) ? value : null; + + public Protoconf.FruitConf.Types.Fruit.Types.Item? FindFirstItem(int price) => + FindItem(price)?.FirstOrDefault(); + + public OrderedIndex_ItemMap? FindItemMap1(int fruitType) => + _orderedIndexItemMap1.TryGetValue(fruitType, out var value) ? value : null; + + public List? FindItem1(int fruitType, int price) => + FindItemMap1(fruitType)?.TryGetValue(price, out var value) == true ? value : null; + + public Protoconf.FruitConf.Types.Fruit.Types.Item? FindFirstItem1(int fruitType, int price) => + FindItem1(fruitType, price)?.FirstOrDefault(); + } + + 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(); + + public static string Name() => Protoconf.Fruit2Conf.Descriptor.Name; + + 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) + { + return false; + } + LoadStats.Duration = DateTime.Now - start; + return ProcessAfterLoad(); + } + + public ref readonly Protoconf.Fruit2Conf Data() => ref _data; + + public override pb::IMessage? Message() => _data; + + 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) + { + var k2 = item3.Key; + 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) + { + var k2 = item3.Key; + { + // 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; + } + + public Protoconf.Fruit2Conf.Types.Fruit? Get1(int fruitType) => + _data.FruitMap?.TryGetValue(fruitType, out var val) == true ? val : null; + + // Index: CountryName + public ref readonly Index_CountryMap FindCountryMap() => ref _indexCountryMap; + + public List? FindCountry(string name) => + _indexCountryMap.TryGetValue(name, out var value) ? value : null; + + public Protoconf.Fruit2Conf.Types.Fruit.Types.Country? FindFirstCountry(string name) => + FindCountry(name)?.FirstOrDefault(); + + // Index: CountryItemAttrName + public ref readonly Index_AttrMap FindAttrMap() => ref _indexAttrMap; + + public List? FindAttr(string name) => + _indexAttrMap.TryGetValue(name, out var value) ? value : null; + + public Protoconf.Fruit2Conf.Types.Fruit.Types.Country.Types.Item.Types.Attr? FindFirstAttr(string name) => + FindAttr(name)?.FirstOrDefault(); + + public Index_AttrMap? FindAttrMap1(int fruitType) => + _indexAttrMap1.TryGetValue(fruitType, out var value) ? value : null; + + public List? FindAttr1(int fruitType, string name) => + FindAttrMap1(fruitType)?.TryGetValue(name, out var value) == true ? value : null; + + public Protoconf.Fruit2Conf.Types.Fruit.Types.Country.Types.Item.Types.Attr? FindFirstAttr1(int fruitType, string name) => + FindAttr1(fruitType, name)?.FirstOrDefault(); + + // OrderedIndex: CountryItemPrice + public ref readonly OrderedIndex_ItemMap FindItemMap() => ref _orderedIndexItemMap; + + public List? FindItem(int price) => + _orderedIndexItemMap.TryGetValue(price, out var value) ? value : null; + + public Protoconf.Fruit2Conf.Types.Fruit.Types.Country.Types.Item? FindFirstItem(int price) => + FindItem(price)?.FirstOrDefault(); + + public OrderedIndex_ItemMap? FindItemMap1(int fruitType) => + _orderedIndexItemMap1.TryGetValue(fruitType, out var value) ? value : null; + + public List? FindItem1(int fruitType, int price) => + FindItemMap1(fruitType)?.TryGetValue(price, out var value) == true ? value : null; + + public Protoconf.Fruit2Conf.Types.Fruit.Types.Country.Types.Item? FindFirstItem1(int fruitType, int price) => + FindItem1(fruitType, price)?.FirstOrDefault(); + } + + 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(); + + public static string Name() => Protoconf.Fruit3Conf.Descriptor.Name; + + 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) + { + return false; + } + LoadStats.Duration = DateTime.Now - start; + return ProcessAfterLoad(); + } + + public ref readonly Protoconf.Fruit3Conf Data() => ref _data; + + public override pb::IMessage? Message() => _data; + + 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) + { + var k1 = item3.Key; + 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) + { + var k1 = item3.Key; + { + // 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 + public ref readonly Index_CountryMap FindCountryMap() => ref _indexCountryMap; + + public List? FindCountry(string name) => + _indexCountryMap.TryGetValue(name, out var value) ? value : null; + + public Protoconf.Fruit3Conf.Types.Fruit.Types.Country? FindFirstCountry(string name) => + FindCountry(name)?.FirstOrDefault(); + + // Index: CountryItemAttrName + public ref readonly Index_AttrMap FindAttrMap() => ref _indexAttrMap; + + public List? FindAttr(string name) => + _indexAttrMap.TryGetValue(name, out var value) ? value : null; + + public Protoconf.Fruit3Conf.Types.Fruit.Types.Country.Types.Item.Types.Attr? FindFirstAttr(string name) => + FindAttr(name)?.FirstOrDefault(); + + // OrderedIndex: CountryItemPrice + public ref readonly OrderedIndex_ItemMap FindItemMap() => ref _orderedIndexItemMap; + + public List? FindItem(int price) => + _orderedIndexItemMap.TryGetValue(price, out var value) ? value : null; + + public Protoconf.Fruit3Conf.Types.Fruit.Types.Country.Types.Item? FindFirstItem(int price) => + FindItem(price)?.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..5cf72671 --- /dev/null +++ b/test/csharp-tableau-loader/tableau/ItemConf.pc.cs @@ -0,0 +1,459 @@ +// +// 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 +{ + 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(); + + public static string Name() => Protoconf.ItemConf.Descriptor.Name; + + 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) + { + return false; + } + LoadStats.Duration = DateTime.Now - start; + return ProcessAfterLoad(); + } + + public ref readonly Protoconf.ItemConf Data() => ref _data; + + public override pb::IMessage? Message() => _data; + + 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) + { + var k1 = item1.Key; + { + // 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) + { + var k1 = item1.Key; + { + // 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; + } + + public Protoconf.ItemConf.Types.Item? Get1(uint id) => + _data.ItemMap?.TryGetValue(id, out var val) == true ? val : null; + + // OrderedMap accessers. + public ref readonly OrderedMap_ItemMap GetOrderedMap() => ref _orderedMap; + + // Index: Type + public ref readonly Index_ItemMap FindItemMap() => ref _indexItemMap; + + public List? FindItem(Protoconf.FruitType type) => + _indexItemMap.TryGetValue(type, out var value) ? value : null; + + public Protoconf.ItemConf.Types.Item? FindFirstItem(Protoconf.FruitType type) => + FindItem(type)?.FirstOrDefault(); + + // Index: Param@ItemInfo + public ref readonly Index_ItemInfoMap FindItemInfoMap() => ref _indexItemInfoMap; + + public List? FindItemInfo(int param) => + _indexItemInfoMap.TryGetValue(param, out var value) ? value : null; + + public Protoconf.ItemConf.Types.Item? FindFirstItemInfo(int param) => + FindItemInfo(param)?.FirstOrDefault(); + + // Index: Default@ItemDefaultInfo + public ref readonly Index_ItemDefaultInfoMap FindItemDefaultInfoMap() => ref _indexItemDefaultInfoMap; + + public List? FindItemDefaultInfo(string default_) => + _indexItemDefaultInfoMap.TryGetValue(default_, out var value) ? value : null; + + public Protoconf.ItemConf.Types.Item? FindFirstItemDefaultInfo(string default_) => + FindItemDefaultInfo(default_)?.FirstOrDefault(); + + // Index: ExtType@ItemExtInfo + public ref readonly Index_ItemExtInfoMap FindItemExtInfoMap() => ref _indexItemExtInfoMap; + + public List? FindItemExtInfo(Protoconf.FruitType extType) => + _indexItemExtInfoMap.TryGetValue(extType, out var value) ? value : null; + + public Protoconf.ItemConf.Types.Item? FindFirstItemExtInfo(Protoconf.FruitType extType) => + FindItemExtInfo(extType)?.FirstOrDefault(); + + // Index: (ID,Name)@AwardItem + public ref readonly Index_AwardItemMap FindAwardItemMap() => ref _indexAwardItemMap; + + public List? FindAwardItem(uint id, string name) => + _indexAwardItemMap.TryGetValue(new Index_AwardItemKey(id, name), out var value) ? value : null; + + public Protoconf.ItemConf.Types.Item? FindFirstAwardItem(uint id, string name) => + FindAwardItem(id, name)?.FirstOrDefault(); + + // Index: (ID,Type,Param,ExtType)@SpecialItem + public ref readonly Index_SpecialItemMap FindSpecialItemMap() => ref _indexSpecialItemMap; + + 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; + + 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 + public ref readonly Index_ItemPathDirMap FindItemPathDirMap() => ref _indexItemPathDirMap; + + public List? FindItemPathDir(string dir) => + _indexItemPathDirMap.TryGetValue(dir, out var value) ? value : null; + + public Protoconf.ItemConf.Types.Item? FindFirstItemPathDir(string dir) => + FindItemPathDir(dir)?.FirstOrDefault(); + + // Index: PathName@ItemPathName + public ref readonly Index_ItemPathNameMap FindItemPathNameMap() => ref _indexItemPathNameMap; + + public List? FindItemPathName(string name) => + _indexItemPathNameMap.TryGetValue(name, out var value) ? value : null; + + public Protoconf.ItemConf.Types.Item? FindFirstItemPathName(string name) => + FindItemPathName(name)?.FirstOrDefault(); + + // Index: PathFriendID@ItemPathFriendID + public ref readonly Index_ItemPathFriendIDMap FindItemPathFriendIDMap() => ref _indexItemPathFriendIdMap; + + public List? FindItemPathFriendID(uint id) => + _indexItemPathFriendIdMap.TryGetValue(id, out var value) ? value : null; + + public Protoconf.ItemConf.Types.Item? FindFirstItemPathFriendID(uint id) => + FindItemPathFriendID(id)?.FirstOrDefault(); + + // Index: UseEffectType@UseEffectType + public ref readonly Index_UseEffectTypeMap FindUseEffectTypeMap() => ref _indexUseEffectTypeMap; + + public List? FindUseEffectType(Protoconf.UseEffect.Types.Type type) => + _indexUseEffectTypeMap.TryGetValue(type, out var value) ? value : null; + + public Protoconf.ItemConf.Types.Item? FindFirstUseEffectType(Protoconf.UseEffect.Types.Type type) => + FindUseEffectType(type)?.FirstOrDefault(); + + // OrderedIndex: ExtType@ExtType + public ref readonly OrderedIndex_ExtTypeMap FindExtTypeMap() => ref _orderedIndexExtTypeMap; + + public List? FindExtType(Protoconf.FruitType extType) => + _orderedIndexExtTypeMap.TryGetValue(extType, out var value) ? value : null; + + public Protoconf.ItemConf.Types.Item? FindFirstExtType(Protoconf.FruitType extType) => + FindExtType(extType)?.FirstOrDefault(); + + // OrderedIndex: (Param,ExtType)@ParamExtType + public ref readonly OrderedIndex_ParamExtTypeMap FindParamExtTypeMap() => ref _orderedIndexParamExtTypeMap; + + public List? FindParamExtType(int param, Protoconf.FruitType extType) => + _orderedIndexParamExtTypeMap.TryGetValue(new OrderedIndex_ParamExtTypeKey(param, extType), out var value) ? value : null; + + 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..cbdea50d --- /dev/null +++ b/test/csharp-tableau-loader/tableau/Load.pc.cs @@ -0,0 +1,120 @@ +// +// 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 +{ + public static class Load + { + public delegate byte[] ReadFunc(string path); + public delegate pb::IMessage? LoadFunc(pbr::MessageDescriptor desc, string dir, Format fmt, in MessagerOptions? options); + + public class BaseOptions + { + public bool? IgnoreUnknownFields { get; set; } + public ReadFunc? ReadFunc { get; set; } + public LoadFunc? LoadFunc { get; set; } + } + + public class Options : BaseOptions + { + public Dictionary? MessagerOptions { get; set; } + 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; + } + } + + public class MessagerOptions : BaseOptions, ICloneable + { + public string? Path { get; set; } + + public object Clone() + { + return new MessagerOptions + { + IgnoreUnknownFields = IgnoreUnknownFields, + ReadFunc = ReadFunc, + LoadFunc = LoadFunc, + Path = Path + }; + } + } + + public static pb::IMessage? LoadMessager(pbr::MessageDescriptor desc, string path, Format fmt, in MessagerOptions? options = null) + { + var readFunc = options?.ReadFunc ?? File.ReadAllBytes; + byte[] content = readFunc(path); + return Unmarshal(content, desc, fmt, options); + } + + 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); + } + + public static pb::IMessage? Unmarshal(byte[] content, pbr::MessageDescriptor desc, Format fmt, in MessagerOptions? options = null) + { + switch (fmt) + { + case Format.JSON: + var parser = new pb::JsonParser( + pb::JsonParser.Settings.Default.WithIgnoreUnknownFields(options?.IgnoreUnknownFields ?? false) + ); + return parser.Parse(new StreamReader(new MemoryStream(content)), desc); + case Format.Bin: + return desc.Parser.ParseFrom(content); + default: + return null; + } + } + } + + public interface IMessagerName + { + static abstract string Name(); + } + + public abstract class Messager + { + public class Stats + { + public TimeSpan Duration; + } + + protected Stats LoadStats = new(); + + public ref readonly Stats GetStats() => ref LoadStats; + + public abstract bool Load(string dir, Format fmt, in Load.MessagerOptions? options = null); + + public virtual pb::IMessage? Message() => null; + + protected virtual bool ProcessAfterLoad() => true; + + 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..bf56fe8c --- /dev/null +++ b/test/csharp-tableau-loader/tableau/PatchConf.pc.cs @@ -0,0 +1,113 @@ +// +// 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 +{ + public class PatchReplaceConf : Messager, IMessagerName + { + private Protoconf.PatchReplaceConf _data = new(); + + public static string Name() => Protoconf.PatchReplaceConf.Descriptor.Name; + + 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) + { + return false; + } + LoadStats.Duration = DateTime.Now - start; + return ProcessAfterLoad(); + } + + public ref readonly Protoconf.PatchReplaceConf Data() => ref _data; + + public override pb::IMessage? Message() => _data; + } + + public class PatchMergeConf : Messager, IMessagerName + { + private Protoconf.PatchMergeConf _data = new(); + + public static string Name() => Protoconf.PatchMergeConf.Descriptor.Name; + + 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) + { + return false; + } + LoadStats.Duration = DateTime.Now - start; + return ProcessAfterLoad(); + } + + public ref readonly Protoconf.PatchMergeConf Data() => ref _data; + + public override pb::IMessage? Message() => _data; + + public Protoconf.Item? Get1(uint id) => + _data.ItemMap?.TryGetValue(id, out var val) == true ? val : null; + } + + public class RecursivePatchConf : Messager, IMessagerName + { + private Protoconf.RecursivePatchConf _data = new(); + + public static string Name() => Protoconf.RecursivePatchConf.Descriptor.Name; + + 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) + { + return false; + } + LoadStats.Duration = DateTime.Now - start; + return ProcessAfterLoad(); + } + + public ref readonly Protoconf.RecursivePatchConf Data() => ref _data; + + public override pb::IMessage? Message() => _data; + + public Protoconf.RecursivePatchConf.Types.Shop? Get1(uint shopId) => + _data.ShopMap?.TryGetValue(shopId, out var val) == true ? val : null; + + public Protoconf.RecursivePatchConf.Types.Shop.Types.Goods? Get2(uint shopId, uint goodsId) => + Get1(shopId)?.GoodsMap?.TryGetValue(goodsId, out var val) == true ? val : null; + + 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; + + 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..29bd49f9 --- /dev/null +++ b/test/csharp-tableau-loader/tableau/TestConf.pc.cs @@ -0,0 +1,603 @@ +// +// 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 +{ + 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(); + + public static string Name() => Protoconf.ActivityConf.Descriptor.Name; + + 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) + { + return false; + } + LoadStats.Duration = DateTime.Now - start; + return ProcessAfterLoad(); + } + + public ref readonly Protoconf.ActivityConf Data() => ref _data; + + public override pb::IMessage? Message() => _data; + + 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) + { + var k3 = item3.Key; + 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; + } + + public Protoconf.ActivityConf.Types.Activity? Get1(ulong activityId) => + _data.ActivityMap?.TryGetValue(activityId, out var val) == true ? val : null; + + public Protoconf.ActivityConf.Types.Activity.Types.Chapter? Get2(ulong activityId, uint chapterId) => + Get1(activityId)?.ChapterMap?.TryGetValue(chapterId, out var val) == true ? val : null; + + public Protoconf.Section? Get3(ulong activityId, uint chapterId, uint sectionId) => + Get2(activityId, chapterId)?.SectionMap?.TryGetValue(sectionId, out var val) == true ? val : null; + + 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. + public ref readonly OrderedMap_ActivityMap GetOrderedMap() => ref _orderedMap; + + public OrderedMap_Activity_ChapterMap? GetOrderedMap1(ulong activityId) => + _orderedMap.TryGetValue(activityId, out var value) ? value.Item1 : null; + + public OrderedMap_protoconf_SectionMap? GetOrderedMap2(ulong activityId, uint chapterId) => + GetOrderedMap1(activityId)?.TryGetValue(chapterId, out var value) == true ? value.Item1 : null; + + public OrderedMap_int32Map? GetOrderedMap3(ulong activityId, uint chapterId, uint sectionId) => + GetOrderedMap2(activityId, chapterId)?.TryGetValue(sectionId, out var value) == true ? value.Item1 : null; + + // Index: ActivityName + public ref readonly Index_ActivityMap FindActivityMap() => ref _indexActivityMap; + + public List? FindActivity(string activityName) => + _indexActivityMap.TryGetValue(activityName, out var value) ? value : null; + + public Protoconf.ActivityConf.Types.Activity? FindFirstActivity(string activityName) => + FindActivity(activityName)?.FirstOrDefault(); + + // Index: ChapterID + public ref readonly Index_ChapterMap FindChapterMap() => ref _indexChapterMap; + + public List? FindChapter(uint chapterId) => + _indexChapterMap.TryGetValue(chapterId, out var value) ? value : null; + + public Protoconf.ActivityConf.Types.Activity.Types.Chapter? FindFirstChapter(uint chapterId) => + FindChapter(chapterId)?.FirstOrDefault(); + + public Index_ChapterMap? FindChapterMap1(ulong activityId) => + _indexChapterMap1.TryGetValue(activityId, out var value) ? value : null; + + public List? FindChapter1(ulong activityId, uint chapterId) => + FindChapterMap1(activityId)?.TryGetValue(chapterId, out var value) == true ? value : null; + + public Protoconf.ActivityConf.Types.Activity.Types.Chapter? FindFirstChapter1(ulong activityId, uint chapterId) => + FindChapter1(activityId, chapterId)?.FirstOrDefault(); + + // Index: ChapterName@NamedChapter + public ref readonly Index_NamedChapterMap FindNamedChapterMap() => ref _indexNamedChapterMap; + + public List? FindNamedChapter(string chapterName) => + _indexNamedChapterMap.TryGetValue(chapterName, out var value) ? value : null; + + public Protoconf.ActivityConf.Types.Activity.Types.Chapter? FindFirstNamedChapter(string chapterName) => + FindNamedChapter(chapterName)?.FirstOrDefault(); + + public Index_NamedChapterMap? FindNamedChapterMap1(ulong activityId) => + _indexNamedChapterMap1.TryGetValue(activityId, out var value) ? value : null; + + public List? FindNamedChapter1(ulong activityId, string chapterName) => + FindNamedChapterMap1(activityId)?.TryGetValue(chapterName, out var value) == true ? value : null; + + public Protoconf.ActivityConf.Types.Activity.Types.Chapter? FindFirstNamedChapter1(ulong activityId, string chapterName) => + FindNamedChapter1(activityId, chapterName)?.FirstOrDefault(); + + // Index: SectionItemID@Award + public ref readonly Index_AwardMap FindAwardMap() => ref _indexAwardMap; + + public List? FindAward(uint id) => + _indexAwardMap.TryGetValue(id, out var value) ? value : null; + + public Protoconf.Section.Types.SectionItem? FindFirstAward(uint id) => + FindAward(id)?.FirstOrDefault(); + + public Index_AwardMap? FindAwardMap1(ulong activityId) => + _indexAwardMap1.TryGetValue(activityId, out var value) ? value : null; + + public List? FindAward1(ulong activityId, uint id) => + FindAwardMap1(activityId)?.TryGetValue(id, out var value) == true ? value : null; + + public Protoconf.Section.Types.SectionItem? FindFirstAward1(ulong activityId, uint id) => + FindAward1(activityId, id)?.FirstOrDefault(); + + public Index_AwardMap? FindAwardMap2(ulong activityId, uint chapterId) => + _indexAwardMap2.TryGetValue(new LevelIndex_Activity_ChapterKey(activityId, chapterId), out var value) ? value : null; + + public List? FindAward2(ulong activityId, uint chapterId, uint id) => + FindAwardMap2(activityId, chapterId)?.TryGetValue(id, out var value) == true ? value : null; + + public Protoconf.Section.Types.SectionItem? FindFirstAward2(ulong activityId, uint chapterId, uint id) => + FindAward2(activityId, chapterId, id)?.FirstOrDefault(); + } + + public class ChapterConf : Messager, IMessagerName + { + private Protoconf.ChapterConf _data = new(); + + public static string Name() => Protoconf.ChapterConf.Descriptor.Name; + + 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) + { + return false; + } + LoadStats.Duration = DateTime.Now - start; + return ProcessAfterLoad(); + } + + public ref readonly Protoconf.ChapterConf Data() => ref _data; + + public override pb::IMessage? Message() => _data; + + public Protoconf.ChapterConf.Types.Chapter? Get1(ulong id) => + _data.ChapterMap?.TryGetValue(id, out var val) == true ? val : null; + } + + public class ThemeConf : Messager, IMessagerName + { + private Protoconf.ThemeConf _data = new(); + + public static string Name() => Protoconf.ThemeConf.Descriptor.Name; + + 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) + { + return false; + } + LoadStats.Duration = DateTime.Now - start; + return ProcessAfterLoad(); + } + + public ref readonly Protoconf.ThemeConf Data() => ref _data; + + public override pb::IMessage? Message() => _data; + + public Protoconf.ThemeConf.Types.Theme? Get1(string name) => + _data.ThemeMap?.TryGetValue(name, out var val) == true ? val : null; + + public string? Get2(string name, string param) => + Get1(name)?.ParamMap?.TryGetValue(param, out var val) == true ? val : null; + } + + 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(); + + public static string Name() => Protoconf.TaskConf.Descriptor.Name; + + 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) + { + return false; + } + LoadStats.Duration = DateTime.Now - start; + return ProcessAfterLoad(); + } + + public ref readonly Protoconf.TaskConf Data() => ref _data; + + public override pb::IMessage? Message() => _data; + + protected override bool ProcessAfterLoad() + { + // Index init. + _indexTaskMap.Clear(); + foreach (var item1 in _data.TaskMap) + { + var k1 = item1.Key; + { + // 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) + { + var k1 = item1.Key; + { + // 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; + } + + public Protoconf.TaskConf.Types.Task? Get1(long id) => + _data.TaskMap?.TryGetValue(id, out var val) == true ? val : null; + + // Index: ActivityID + public ref readonly Index_TaskMap FindTaskMap() => ref _indexTaskMap; + + public List? FindTask(long activityId) => + _indexTaskMap.TryGetValue(activityId, out var value) ? value : null; + + public Protoconf.TaskConf.Types.Task? FindFirstTask(long activityId) => + FindTask(activityId)?.FirstOrDefault(); + + // OrderedIndex: Goal@OrderedTask + public ref readonly OrderedIndex_OrderedTaskMap FindOrderedTaskMap() => ref _orderedIndexOrderedTaskMap; + + public List? FindOrderedTask(long goal) => + _orderedIndexOrderedTaskMap.TryGetValue(goal, out var value) ? value : null; + + public Protoconf.TaskConf.Types.Task? FindFirstOrderedTask(long goal) => + FindOrderedTask(goal)?.FirstOrDefault(); + + // OrderedIndex: Expiry@TaskExpiry + public ref readonly OrderedIndex_TaskExpiryMap FindTaskExpiryMap() => ref _orderedIndexTaskExpiryMap; + + public List? FindTaskExpiry(long expiry) => + _orderedIndexTaskExpiryMap.TryGetValue(expiry, out var value) ? value : null; + + public Protoconf.TaskConf.Types.Task? FindFirstTaskExpiry(long expiry) => + FindTaskExpiry(expiry)?.FirstOrDefault(); + + // OrderedIndex: Expiry@SortedTaskExpiry + public ref readonly OrderedIndex_SortedTaskExpiryMap FindSortedTaskExpiryMap() => ref _orderedIndexSortedTaskExpiryMap; + + public List? FindSortedTaskExpiry(long expiry) => + _orderedIndexSortedTaskExpiryMap.TryGetValue(expiry, out var value) ? value : null; + + public Protoconf.TaskConf.Types.Task? FindFirstSortedTaskExpiry(long expiry) => + FindSortedTaskExpiry(expiry)?.FirstOrDefault(); + + // OrderedIndex: (Expiry,ActivityID)@ActivityExpiry + public ref readonly OrderedIndex_ActivityExpiryMap FindActivityExpiryMap() => ref _orderedIndexActivityExpiryMap; + + public List? FindActivityExpiry(long expiry, long activityId) => + _orderedIndexActivityExpiryMap.TryGetValue(new OrderedIndex_ActivityExpiryKey(expiry, activityId), out var value) ? value : null; + + 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..c3052e59 --- /dev/null +++ b/test/csharp-tableau-loader/tableau/Util.pc.cs @@ -0,0 +1,45 @@ +// +// 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 +{ + public enum Format + { + Unknown, + JSON, + Bin + } + + public static class Util + { + + private const string _unknownExt = ".unknown"; + private const string _jsonExt = ".json"; + private const string _binExt = ".binpb"; + + public static Format GetFormat(string path) + { + string ext = Path.GetExtension(path); + return ext switch + { + _jsonExt => Format.JSON, + _binExt => Format.Bin, + _ => Format.Unknown, + }; + } + + 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,