From 170af6bac2ceacbdc1c09f0370327f370a14daf7 Mon Sep 17 00:00:00 2001 From: evilolipop Date: Wed, 13 Dec 2023 14:06:53 +0800 Subject: [PATCH] feat: add new protoc-gen-go-grain --- protobuf/protoc-gen-go-grain/Makefile | 3 + protobuf/protoc-gen-go-grain/generate.go | 99 +++++++++++ protobuf/protoc-gen-go-grain/main.go | 18 ++ .../protoc-gen-go-grain.sh | 3 + protobuf/protoc-gen-go-grain/template.go | 36 ++++ .../protoc-gen-go-grain/templates/grain.tmpl | 143 ++++++++++++++++ .../testdata/hello/hello.pb.go | 157 ++++++++++++++++++ .../testdata/hello/hello.proto | 15 ++ .../testdata/hello/hello_grain.pb.go | 157 ++++++++++++++++++ protobuf/protoc-gen-go-grain/version.go | 3 + 10 files changed, 634 insertions(+) create mode 100644 protobuf/protoc-gen-go-grain/Makefile create mode 100644 protobuf/protoc-gen-go-grain/generate.go create mode 100644 protobuf/protoc-gen-go-grain/main.go create mode 100755 protobuf/protoc-gen-go-grain/protoc-gen-go-grain.sh create mode 100644 protobuf/protoc-gen-go-grain/template.go create mode 100644 protobuf/protoc-gen-go-grain/templates/grain.tmpl create mode 100644 protobuf/protoc-gen-go-grain/testdata/hello/hello.pb.go create mode 100644 protobuf/protoc-gen-go-grain/testdata/hello/hello.proto create mode 100644 protobuf/protoc-gen-go-grain/testdata/hello/hello_grain.pb.go create mode 100644 protobuf/protoc-gen-go-grain/version.go diff --git a/protobuf/protoc-gen-go-grain/Makefile b/protobuf/protoc-gen-go-grain/Makefile new file mode 100644 index 00000000..156e359a --- /dev/null +++ b/protobuf/protoc-gen-go-grain/Makefile @@ -0,0 +1,3 @@ +.PHONY: gentestdata +gentestdata: + protoc --go_out=. --go_opt=paths=source_relative --plugin=protoc-gen-go-grain=protoc-gen-go-grain.sh --go-grain_out=. --go-grain_opt=paths=source_relative testdata/hello/hello.proto diff --git a/protobuf/protoc-gen-go-grain/generate.go b/protobuf/protoc-gen-go-grain/generate.go new file mode 100644 index 00000000..e3614669 --- /dev/null +++ b/protobuf/protoc-gen-go-grain/generate.go @@ -0,0 +1,99 @@ +package main + +import ( + "fmt" + + "google.golang.org/protobuf/compiler/protogen" + "google.golang.org/protobuf/types/descriptorpb" +) + +const deprecationComment = "// Deprecated: Do not use." + +const ( + timePackage = protogen.GoImportPath("time") + errorsPackage = protogen.GoImportPath("errors") + fmtPackage = protogen.GoImportPath("fmt") + slogPackage = protogen.GoImportPath("log/slog") + protoPackage = protogen.GoImportPath("google.golang.org/protobuf/proto") + actorPackage = protogen.GoImportPath("github.com/asynkron/protoactor-go/actor") + clusterPackage = protogen.GoImportPath("github.com/asynkron/protoactor-go/cluster") +) + +func generateFile(gen *protogen.Plugin, file *protogen.File) { + filename := file.GeneratedFilenamePrefix + "_grain.pb.go" + g := gen.NewGeneratedFile(filename, file.GoImportPath) + + generateHeader(gen, g, file) + generateContent(gen, g, file) +} + +func generateHeader(gen *protogen.Plugin, g *protogen.GeneratedFile, file *protogen.File) { + g.P("// Code generated by protoc-gen-grain. DO NOT EDIT.") + g.P("// versions:") + g.P("// protoc-gen-grain ", version) + protocVersion := "(unknown)" + if v := gen.Request.GetCompilerVersion(); v != nil { + protocVersion = fmt.Sprintf("v%v.%v.%v", v.GetMajor(), v.GetMinor(), v.GetPatch()) + if s := v.GetSuffix(); s != "" { + protocVersion += "-" + s + } + } + g.P("// protoc ", protocVersion) + if file.Proto.GetOptions().GetDeprecated() { + g.P("// ", file.Desc.Path(), " is a deprecated file.") + } else { + g.P("// source: ", file.Desc.Path()) + } + g.P() +} + +func generateContent(gen *protogen.Plugin, g *protogen.GeneratedFile, file *protogen.File) { + g.P("package ", file.GoPackageName) + g.P() + + if len(file.Services) == 0 { + return + } + + g.QualifiedGoIdent(actorPackage.Ident("")) + g.QualifiedGoIdent(clusterPackage.Ident("")) + g.QualifiedGoIdent(protoPackage.Ident("")) + g.QualifiedGoIdent(errorsPackage.Ident("")) + g.QualifiedGoIdent(fmtPackage.Ident("")) + g.QualifiedGoIdent(timePackage.Ident("")) + g.QualifiedGoIdent(slogPackage.Ident("")) + + for _, service := range file.Services { + generateService(service, file, g) + } +} + +func generateService(service *protogen.Service, file *protogen.File, g *protogen.GeneratedFile) { + if service.Desc.Options().(*descriptorpb.ServiceOptions).GetDeprecated() { + g.P("//") + g.P(deprecationComment) + } + + sd := &serviceDesc{ + Name: service.GoName, + } + + for i, method := range service.Methods { + if method.Desc.IsStreamingClient() || method.Desc.IsStreamingServer() { + continue + } + + md := &methodDesc{ + Name: method.GoName, + Input: g.QualifiedGoIdent(method.Input.GoIdent), + Output: g.QualifiedGoIdent(method.Output.GoIdent), + Index: i, + } + + sd.Methods = append(sd.Methods, md) + } + + if len(sd.Methods) != 0 { + g.P(sd.execute()) + } +} diff --git a/protobuf/protoc-gen-go-grain/main.go b/protobuf/protoc-gen-go-grain/main.go new file mode 100644 index 00000000..ed8a8c8f --- /dev/null +++ b/protobuf/protoc-gen-go-grain/main.go @@ -0,0 +1,18 @@ +package main + +import ( + "google.golang.org/protobuf/compiler/protogen" +) + +func main() { + protogen.Options{}.Run(func(gen *protogen.Plugin) error { + for _, f := range gen.Files { + if !f.Generate { + continue + } + generateFile(gen, f) + } + + return nil + }) +} diff --git a/protobuf/protoc-gen-go-grain/protoc-gen-go-grain.sh b/protobuf/protoc-gen-go-grain/protoc-gen-go-grain.sh new file mode 100755 index 00000000..9235e27a --- /dev/null +++ b/protobuf/protoc-gen-go-grain/protoc-gen-go-grain.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +exec go run . diff --git a/protobuf/protoc-gen-go-grain/template.go b/protobuf/protoc-gen-go-grain/template.go new file mode 100644 index 00000000..ce1bc545 --- /dev/null +++ b/protobuf/protoc-gen-go-grain/template.go @@ -0,0 +1,36 @@ +package main + +import ( + "bytes" + _ "embed" + "strings" + "text/template" +) + +//go:embed templates/grain.tmpl +var grainTemplate string + +type serviceDesc struct { + Name string // Greeter + Methods []*methodDesc +} + +type methodDesc struct { + Name string + Input string + Output string + Index int +} + +func (s *serviceDesc) execute() string { + buf := new(bytes.Buffer) + tmpl, err := template.New("grain").Parse(strings.TrimSpace(grainTemplate)) + if err != nil { + panic(err) + } + if err := tmpl.Execute(buf, s); err != nil { + panic(err) + } + + return strings.Trim(buf.String(), "\r\n") +} diff --git a/protobuf/protoc-gen-go-grain/templates/grain.tmpl b/protobuf/protoc-gen-go-grain/templates/grain.tmpl new file mode 100644 index 00000000..69a72136 --- /dev/null +++ b/protobuf/protoc-gen-go-grain/templates/grain.tmpl @@ -0,0 +1,143 @@ +{{ $service := . }} +var x{{ $service.Name }}Factory func() {{ $service.Name }} + +// {{ $service.Name }}Factory produces a {{ $service.Name }} +func {{ $service.Name }}Factory(factory func() {{ $service.Name }}) { + x{{ $service.Name }}Factory = factory +} + +// Get{{ $service.Name }}GrainClient instantiates a new {{ $service.Name }}GrainClient with given Identity +func Get{{ $service.Name }}GrainClient(c *cluster.Cluster, id string) *{{ $service.Name }}GrainClient { + if c == nil { + panic(fmt.Errorf("nil cluster instance")) + } + if id == "" { + panic(fmt.Errorf("empty id")) + } + return &{{ $service.Name }}GrainClient{Identity: id, cluster: c} +} + +// Get{{ $service.Name }}Kind instantiates a new cluster.Kind for {{ $service.Name }} +func Get{{ $service.Name }}Kind(opts ...actor.PropsOption) *cluster.Kind { + props := actor.PropsFromProducer(func() actor.Actor { + return &{{ $service.Name }}Actor{ + Timeout: 60 * time.Second, + } + }, opts...) + kind := cluster.NewKind("{{ $service.Name }}", props) + return kind +} + +// Get{{ $service.Name }}Kind instantiates a new cluster.Kind for {{ $service.Name }} +func New{{ $service.Name }}Kind(factory func() {{ $service.Name }}, timeout time.Duration, opts ...actor.PropsOption) *cluster.Kind { + x{{ $service.Name }}Factory = factory + props := actor.PropsFromProducer(func() actor.Actor { + return &{{ $service.Name }}Actor{ + Timeout: timeout, + } + }, opts...) + kind := cluster.NewKind("{{ $service.Name }}", props) + return kind +} + +// {{ $service.Name }} interfaces the services available to the {{ $service.Name }} +type {{ $service.Name }} interface { + Init(ctx cluster.GrainContext) + Terminate(ctx cluster.GrainContext) + ReceiveDefault(ctx cluster.GrainContext) + {{- range $method := .Methods }} + {{ $method.Name }}(*{{ $method.Input }}, cluster.GrainContext) (*{{ $method.Output }}, error) + {{- end }} +} + +// {{ $service.Name }}GrainClient holds the base data for the {{ $service.Name }}Grain +type {{ $service.Name }}GrainClient struct { + Identity string + cluster *cluster.Cluster +} +{{ range $method := .Methods}} +// {{ $method.Name }} requests the execution on to the cluster with CallOptions +func (g *{{ $service.Name }}GrainClient) {{ $method.Name }}(r *{{ $method.Input }}, opts ...cluster.GrainCallOption) (*{{ $method.Output }}, error) { + bytes, err := proto.Marshal(r) + if err != nil { + return nil, err + } + reqMsg := &cluster.GrainRequest{MethodIndex: {{ $method.Index }}, MessageData: bytes} + resp, err := g.cluster.Request(g.Identity, "{{ $service.Name }}", reqMsg, opts...) + if err != nil { + return nil, err + } + switch msg := resp.(type) { + case *cluster.GrainResponse: + result := &{{ $method.Output }}{} + err = proto.Unmarshal(msg.MessageData, result) + if err != nil { + return nil, err + } + return result, nil + case *cluster.GrainErrorResponse: + return nil, errors.New(msg.Err) + default: + return nil, errors.New("unknown response") + } +} +{{ end }} +// {{ $service.Name }}Actor represents the actor structure +type {{ $service.Name }}Actor struct { + ctx cluster.GrainContext + inner {{ $service.Name }} + Timeout time.Duration +} + +// Receive ensures the lifecycle of the actor for the received message +func (a *{{ $service.Name }}Actor) Receive(ctx actor.Context) { + switch msg := ctx.Message().(type) { + case *actor.Started: //pass + case *cluster.ClusterInit: + a.ctx = cluster.NewGrainContext(ctx, msg.Identity, msg.Cluster) + a.inner = x{{ $service.Name }}Factory() + a.inner.Init(a.ctx) + + if a.Timeout > 0 { + ctx.SetReceiveTimeout(a.Timeout) + } + case *actor.ReceiveTimeout: + ctx.Poison(ctx.Self()) + case *actor.Stopped: + a.inner.Terminate(a.ctx) + case actor.AutoReceiveMessage: // pass + case actor.SystemMessage: // pass + + case *cluster.GrainRequest: + switch msg.MethodIndex { + {{ range $method := .Methods -}} + case {{ $method.Index }}: + req := &{{ $method.Input }}{} + err := proto.Unmarshal(msg.MessageData, req) + if err != nil { + ctx.Logger().Error("[Grain] {{ $method.Name }}({{ $method.Input }}) proto.Unmarshal failed.", slog.Any("error", err)) + resp := &cluster.GrainErrorResponse{Err: err.Error()} + ctx.Respond(resp) + return + } + r0, err := a.inner.{{ $method.Name }}(req, a.ctx) + if err != nil { + resp := &cluster.GrainErrorResponse{Err: err.Error()} + ctx.Respond(resp) + return + } + bytes, err := proto.Marshal(r0) + if err != nil { + ctx.Logger().Error("[Grain] {{ $method.Name }}({{ $method.Input }}) proto.Marshal failed", slog.Any("error", err)) + resp := &cluster.GrainErrorResponse{Err: err.Error()} + ctx.Respond(resp) + return + } + resp := &cluster.GrainResponse{MessageData: bytes} + ctx.Respond(resp) + {{ end -}} + } + default: + a.inner.ReceiveDefault(a.ctx) + } +} \ No newline at end of file diff --git a/protobuf/protoc-gen-go-grain/testdata/hello/hello.pb.go b/protobuf/protoc-gen-go-grain/testdata/hello/hello.pb.go new file mode 100644 index 00000000..55dab641 --- /dev/null +++ b/protobuf/protoc-gen-go-grain/testdata/hello/hello.pb.go @@ -0,0 +1,157 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.30.0 +// protoc v4.25.0 +// source: testdata/hello/hello.proto + +package hello + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + emptypb "google.golang.org/protobuf/types/known/emptypb" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type SayHelloResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"` +} + +func (x *SayHelloResponse) Reset() { + *x = SayHelloResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_testdata_hello_hello_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SayHelloResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SayHelloResponse) ProtoMessage() {} + +func (x *SayHelloResponse) ProtoReflect() protoreflect.Message { + mi := &file_testdata_hello_hello_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SayHelloResponse.ProtoReflect.Descriptor instead. +func (*SayHelloResponse) Descriptor() ([]byte, []int) { + return file_testdata_hello_hello_proto_rawDescGZIP(), []int{0} +} + +func (x *SayHelloResponse) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +var File_testdata_hello_hello_proto protoreflect.FileDescriptor + +var file_testdata_hello_hello_proto_rawDesc = []byte{ + 0x0a, 0x1a, 0x74, 0x65, 0x73, 0x74, 0x64, 0x61, 0x74, 0x61, 0x2f, 0x68, 0x65, 0x6c, 0x6c, 0x6f, + 0x2f, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x05, 0x68, 0x65, + 0x6c, 0x6c, 0x6f, 0x1a, 0x1b, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x22, 0x2c, 0x0a, 0x10, 0x53, 0x61, 0x79, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x32, 0x46, + 0x0a, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x12, 0x3d, 0x0a, 0x08, 0x53, 0x61, 0x79, 0x48, 0x65, + 0x6c, 0x6c, 0x6f, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x17, 0x2e, 0x68, 0x65, + 0x6c, 0x6c, 0x6f, 0x2e, 0x53, 0x61, 0x79, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x46, 0x5a, 0x44, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, + 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x73, 0x79, 0x6e, 0x6b, 0x72, 0x6f, 0x6e, 0x2f, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x2d, 0x67, 0x6f, 0x2f, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x63, 0x2d, 0x67, 0x65, 0x6e, 0x2d, 0x67, 0x6f, 0x2d, 0x67, 0x72, 0x61, 0x69, 0x6e, 0x2f, + 0x74, 0x65, 0x73, 0x74, 0x64, 0x61, 0x74, 0x61, 0x2f, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x62, 0x06, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_testdata_hello_hello_proto_rawDescOnce sync.Once + file_testdata_hello_hello_proto_rawDescData = file_testdata_hello_hello_proto_rawDesc +) + +func file_testdata_hello_hello_proto_rawDescGZIP() []byte { + file_testdata_hello_hello_proto_rawDescOnce.Do(func() { + file_testdata_hello_hello_proto_rawDescData = protoimpl.X.CompressGZIP(file_testdata_hello_hello_proto_rawDescData) + }) + return file_testdata_hello_hello_proto_rawDescData +} + +var file_testdata_hello_hello_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_testdata_hello_hello_proto_goTypes = []interface{}{ + (*SayHelloResponse)(nil), // 0: hello.SayHelloResponse + (*emptypb.Empty)(nil), // 1: google.protobuf.Empty +} +var file_testdata_hello_hello_proto_depIdxs = []int32{ + 1, // 0: hello.Hello.SayHello:input_type -> google.protobuf.Empty + 0, // 1: hello.Hello.SayHello:output_type -> hello.SayHelloResponse + 1, // [1:2] is the sub-list for method output_type + 0, // [0:1] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_testdata_hello_hello_proto_init() } +func file_testdata_hello_hello_proto_init() { + if File_testdata_hello_hello_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_testdata_hello_hello_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SayHelloResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_testdata_hello_hello_proto_rawDesc, + NumEnums: 0, + NumMessages: 1, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_testdata_hello_hello_proto_goTypes, + DependencyIndexes: file_testdata_hello_hello_proto_depIdxs, + MessageInfos: file_testdata_hello_hello_proto_msgTypes, + }.Build() + File_testdata_hello_hello_proto = out.File + file_testdata_hello_hello_proto_rawDesc = nil + file_testdata_hello_hello_proto_goTypes = nil + file_testdata_hello_hello_proto_depIdxs = nil +} diff --git a/protobuf/protoc-gen-go-grain/testdata/hello/hello.proto b/protobuf/protoc-gen-go-grain/testdata/hello/hello.proto new file mode 100644 index 00000000..f7d181d7 --- /dev/null +++ b/protobuf/protoc-gen-go-grain/testdata/hello/hello.proto @@ -0,0 +1,15 @@ +syntax = "proto3"; + +package hello; + +import "google/protobuf/empty.proto"; + +option go_package = "github.com/asynkron/protoactor-go/protoc-gen-go-grain/testdata/hello"; + +message SayHelloResponse { + string message = 1; +} + +service Hello { + rpc SayHello (google.protobuf.Empty) returns (SayHelloResponse) {} +} \ No newline at end of file diff --git a/protobuf/protoc-gen-go-grain/testdata/hello/hello_grain.pb.go b/protobuf/protoc-gen-go-grain/testdata/hello/hello_grain.pb.go new file mode 100644 index 00000000..81f15f09 --- /dev/null +++ b/protobuf/protoc-gen-go-grain/testdata/hello/hello_grain.pb.go @@ -0,0 +1,157 @@ +// Code generated by protoc-gen-grain. DO NOT EDIT. +// versions: +// protoc-gen-grain v0.1.0 +// protoc v4.25.0 +// source: testdata/hello/hello.proto + +package hello + +import ( + errors "errors" + fmt "fmt" + actor "github.com/asynkron/protoactor-go/actor" + cluster "github.com/asynkron/protoactor-go/cluster" + proto "google.golang.org/protobuf/proto" + emptypb "google.golang.org/protobuf/types/known/emptypb" + slog "log/slog" + time "time" +) + +var xHelloFactory func() Hello + +// HelloFactory produces a Hello +func HelloFactory(factory func() Hello) { + xHelloFactory = factory +} + +// GetHelloGrainClient instantiates a new HelloGrainClient with given Identity +func GetHelloGrainClient(c *cluster.Cluster, id string) *HelloGrainClient { + if c == nil { + panic(fmt.Errorf("nil cluster instance")) + } + if id == "" { + panic(fmt.Errorf("empty id")) + } + return &HelloGrainClient{Identity: id, cluster: c} +} + +// GetHelloKind instantiates a new cluster.Kind for Hello +func GetHelloKind(opts ...actor.PropsOption) *cluster.Kind { + props := actor.PropsFromProducer(func() actor.Actor { + return &HelloActor{ + Timeout: 60 * time.Second, + } + }, opts...) + kind := cluster.NewKind("Hello", props) + return kind +} + +// GetHelloKind instantiates a new cluster.Kind for Hello +func NewHelloKind(factory func() Hello, timeout time.Duration, opts ...actor.PropsOption) *cluster.Kind { + xHelloFactory = factory + props := actor.PropsFromProducer(func() actor.Actor { + return &HelloActor{ + Timeout: timeout, + } + }, opts...) + kind := cluster.NewKind("Hello", props) + return kind +} + +// Hello interfaces the services available to the Hello +type Hello interface { + Init(ctx cluster.GrainContext) + Terminate(ctx cluster.GrainContext) + ReceiveDefault(ctx cluster.GrainContext) + SayHello(*emptypb.Empty, cluster.GrainContext) (*SayHelloResponse, error) +} + +// HelloGrainClient holds the base data for the HelloGrain +type HelloGrainClient struct { + Identity string + cluster *cluster.Cluster +} + +// SayHello requests the execution on to the cluster with CallOptions +func (g *HelloGrainClient) SayHello(r *emptypb.Empty, opts ...cluster.GrainCallOption) (*SayHelloResponse, error) { + bytes, err := proto.Marshal(r) + if err != nil { + return nil, err + } + reqMsg := &cluster.GrainRequest{MethodIndex: 0, MessageData: bytes} + resp, err := g.cluster.Request(g.Identity, "Hello", reqMsg, opts...) + if err != nil { + return nil, err + } + switch msg := resp.(type) { + case *cluster.GrainResponse: + result := &SayHelloResponse{} + err = proto.Unmarshal(msg.MessageData, result) + if err != nil { + return nil, err + } + return result, nil + case *cluster.GrainErrorResponse: + return nil, errors.New(msg.Err) + default: + return nil, errors.New("unknown response") + } +} + +// HelloActor represents the actor structure +type HelloActor struct { + ctx cluster.GrainContext + inner Hello + Timeout time.Duration +} + +// Receive ensures the lifecycle of the actor for the received message +func (a *HelloActor) Receive(ctx actor.Context) { + switch msg := ctx.Message().(type) { + case *actor.Started: //pass + case *cluster.ClusterInit: + a.ctx = cluster.NewGrainContext(ctx, msg.Identity, msg.Cluster) + a.inner = xHelloFactory() + a.inner.Init(a.ctx) + + if a.Timeout > 0 { + ctx.SetReceiveTimeout(a.Timeout) + } + case *actor.ReceiveTimeout: + ctx.Poison(ctx.Self()) + case *actor.Stopped: + a.inner.Terminate(a.ctx) + case actor.AutoReceiveMessage: // pass + case actor.SystemMessage: // pass + + case *cluster.GrainRequest: + switch msg.MethodIndex { + case 0: + req := &emptypb.Empty{} + err := proto.Unmarshal(msg.MessageData, req) + if err != nil { + ctx.Logger().Error("[Grain] SayHello(emptypb.Empty) proto.Unmarshal failed.", slog.Any("error", err)) + resp := &cluster.GrainErrorResponse{Err: err.Error()} + ctx.Respond(resp) + return + } + r0, err := a.inner.SayHello(req, a.ctx) + if err != nil { + resp := &cluster.GrainErrorResponse{Err: err.Error()} + ctx.Respond(resp) + return + } + bytes, err := proto.Marshal(r0) + if err != nil { + ctx.Logger().Error("[Grain] SayHello(emptypb.Empty) proto.Marshal failed", slog.Any("error", err)) + resp := &cluster.GrainErrorResponse{Err: err.Error()} + ctx.Respond(resp) + return + } + resp := &cluster.GrainResponse{MessageData: bytes} + ctx.Respond(resp) + } + default: + a.inner.ReceiveDefault(a.ctx) + } +} diff --git a/protobuf/protoc-gen-go-grain/version.go b/protobuf/protoc-gen-go-grain/version.go new file mode 100644 index 00000000..f282678c --- /dev/null +++ b/protobuf/protoc-gen-go-grain/version.go @@ -0,0 +1,3 @@ +package main + +const version = "v0.1.0"