From 561ea0c6d9a5859042543b4256ad9b6801fde14d Mon Sep 17 00:00:00 2001 From: Elena Kolevska Date: Tue, 26 Nov 2024 18:28:51 +0000 Subject: [PATCH] Fixes scheduler reminders app (#241) * fixes reminders Signed-off-by: Elena Kolevska * removes unnecessary context Signed-off-by: Elena Kolevska * cleanup Signed-off-by: Elena Kolevska * Uses stub for actor method calls Signed-off-by: Elena Kolevska * More cleanup Signed-off-by: Elena Kolevska * reorganises code to follow structure in other examples Signed-off-by: Elena Kolevska * nil check Signed-off-by: Elena Kolevska * Apply suggestions from code review Co-authored-by: Cassie Coyle Signed-off-by: Elena Kolevska * clean up err check Signed-off-by: Elena Kolevska * duh Signed-off-by: Elena Kolevska --------- Signed-off-by: Elena Kolevska Signed-off-by: Elena Kolevska Co-authored-by: Cassie Coyle --- .gitignore | 3 + scheduler-actor-reminders/Dockerfile-server | 6 +- scheduler-actor-reminders/api/api.go | 96 ++------ .../client/player-actor-client.go | 208 +++++++----------- scheduler-actor-reminders/go.mod | 6 +- scheduler-actor-reminders/go.sum | 5 +- .../server/player-actor-server.go | 60 +++++ .../server/player-actor.go | 119 ++++++---- 8 files changed, 252 insertions(+), 251 deletions(-) create mode 100644 scheduler-actor-reminders/server/player-actor-server.go diff --git a/.gitignore b/.gitignore index ecc11929..f034ae0e 100644 --- a/.gitignore +++ b/.gitignore @@ -86,3 +86,6 @@ bld/ # VS Code .vscode/ + +# JetBrains +.idea diff --git a/scheduler-actor-reminders/Dockerfile-server b/scheduler-actor-reminders/Dockerfile-server index 2a71e46a..cf50e642 100644 --- a/scheduler-actor-reminders/Dockerfile-server +++ b/scheduler-actor-reminders/Dockerfile-server @@ -9,7 +9,7 @@ RUN go mod download COPY . . # Build the server app binary -RUN CGO_ENABLED=0 go build -o player-actor ./server/player-actor.go +RUN CGO_ENABLED=0 go build -o player-actor-server ./server # Final stage FROM alpine:latest @@ -17,9 +17,9 @@ FROM alpine:latest WORKDIR /server # Copy binary from the builder stage -COPY --from=builder /server/player-actor . +COPY --from=builder /server/player-actor-server . EXPOSE 3007 # Start the server -CMD ["/server/player-actor"] +CMD ["/server/player-actor-server"] diff --git a/scheduler-actor-reminders/api/api.go b/scheduler-actor-reminders/api/api.go index 4871b691..57c0f3dc 100644 --- a/scheduler-actor-reminders/api/api.go +++ b/scheduler-actor-reminders/api/api.go @@ -2,23 +2,27 @@ package api import ( "context" - "fmt" - - "github.com/dapr/go-sdk/actor" - dapr "github.com/dapr/go-sdk/client" - "github.com/dapr/go-sdk/examples/actor/api" ) -const playerActorType = "playerActorType" +const PlayerActorType = "playerActorType" + +type ClientStub struct { + ActorID string + OnActivate func(context.Context) error + GetUser func(ctx context.Context) (*GetPlayerResponse, error) + Invoke func(context.Context, string) (string, error) + RevivePlayer func(context.Context, string) error + StartReminder func(context.Context, *ReminderRequest) error + StopReminder func(context.Context, *ReminderRequest) error + ReminderCall func(string, []byte, string, string) error +} -type PlayerActor struct { - actor.ServerImplBaseCtx - DaprClient dapr.Client - Health int +func (a *ClientStub) Type() string { + return PlayerActorType } -func (p *PlayerActor) Type() string { - return playerActorType +func (a *ClientStub) ID() string { + return a.ActorID } type GetPlayerRequest struct { @@ -30,67 +34,9 @@ type GetPlayerResponse struct { Health int } -// GetUser retrieving the state of the PlayerActor -func (p *PlayerActor) GetUser(ctx context.Context, player *GetPlayerRequest) (*GetPlayerResponse, error) { - if player.ActorID == p.ID() { - fmt.Printf("Player Actor ID: %s has a health level of: %d\n", p.ID(), p.Health) - return &GetPlayerResponse{ - ActorID: p.ID(), - Health: p.Health, - }, nil - } - return nil, nil -} - -// Invoke invokes an action on the actor -func (p *PlayerActor) Invoke(ctx context.Context, req string) (string, error) { - fmt.Println("get req = ", req) - return req, nil -} - -// RevivePlayer revives the actor players health back to 100 -func (p *PlayerActor) RevivePlayer(ctx context.Context, id string) error { - if id == p.ID() { - fmt.Printf("Reviving player: %s\n", id) - p.Health = 100 - } - - return nil -} - -// ReminderCall executes logic to handle what happens when the reminder is triggered -// Dapr automatically calls this method when a reminder fires for the player actor -func (p *PlayerActor) ReminderCall(reminderName string, state []byte, dueTime string, period string) { - fmt.Println("receive reminder = ", reminderName, " state = ", string(state), "duetime = ", dueTime, "period = ", period) - if reminderName == "healthReminder" { - // Increase health if below 100 - if p.Health < 100 { - p.Health += 10 - if p.Health > 100 { - p.Health = 100 - } - fmt.Printf("Player Actor health increased. Current health: %d\n", p.Health) - } - } else if reminderName == "healthDecayReminder" { - // Decrease health - p.Health -= 5 - if p.Health < 0 { - fmt.Println("Player Actor died...") - } - fmt.Printf("Health decreased. Current health: %d\n", p.Health) - } - -} - -// StartReminder registers a reminder for the actor -func (p *PlayerActor) StartReminder(ctx context.Context, req *api.ReminderRequest) error { - fmt.Println("Starting reminder:", req.ReminderName) - return p.DaprClient.RegisterActorReminder(ctx, &dapr.RegisterActorReminderRequest{ - ActorType: p.Type(), - ActorID: p.ID(), - Name: req.ReminderName, - DueTime: req.Duration, - Period: req.Period, - Data: []byte(req.Data), - }) +type ReminderRequest struct { + ReminderName string `json:"reminder_name"` + DueTime string `json:"due_time"` + Period string `json:"period"` + Data string `json:"data"` } diff --git a/scheduler-actor-reminders/client/player-actor-client.go b/scheduler-actor-reminders/client/player-actor-client.go index 26838814..9554e3ab 100644 --- a/scheduler-actor-reminders/client/player-actor-client.go +++ b/scheduler-actor-reminders/client/player-actor-client.go @@ -2,7 +2,6 @@ package main import ( "context" - "encoding/json" "log" "os" "os/signal" @@ -23,124 +22,48 @@ func main() { panic(err) } defer client.Close() + if err := client.Wait(ctx, 15*time.Second); err != nil { + log.Fatalf("error waiting for Dapr initialization: %v", err) + } - // Actor ID for the player 'session' - actorID := "player-1" - deathSignal := make(chan bool) + // Implement the actor client stub + myActor := &api.ClientStub{ + ActorID: "player-1", + } + client.ImplActorClientStub(myActor) // Start monitoring actor player's health - go monitorPlayerHealth(ctx, client, actorID, deathSignal) + go monitorPlayerHealth(ctx, myActor) - incReminderCtx, incReminderCancel := context.WithTimeout(ctx, 5*time.Second) - defer incReminderCancel() // Start player actor health increase reminder - err = client.RegisterActorReminder(incReminderCtx, &dapr.RegisterActorReminderRequest{ - ActorType: "playerActorType", - ActorID: actorID, - Name: "healthReminder", - DueTime: "10s", - Period: "20s", // Every 20 seconds, increase health - Data: []byte(`"Health increase reminder"`), + err = myActor.StartReminder(ctx, &api.ReminderRequest{ + ReminderName: "healthReminder", + Period: "20s", + DueTime: "10s", + Data: `"Health increase reminder"`, }) if err != nil { - log.Printf("error starting health increase reminder: %v", err) + // The first reminder registrations have to succeed, + // if they don't, the app is not testing what we need to test and we need to exit + log.Fatalf("error starting health increase reminder: %v", err) } - log.Println("Started healthReminder for actor:", actorID) + defer stopReminder(ctx, myActor, "healthReminder") + log.Println("Started healthReminder for actor:", myActor.ID()) - decReminderCtx, decReminderCancel := context.WithTimeout(ctx, 5*time.Second) - defer decReminderCancel() // Start player actor health decay reminder - err = client.RegisterActorReminder(decReminderCtx, &dapr.RegisterActorReminderRequest{ - ActorType: "playerActorType", - ActorID: actorID, - Name: "healthDecayReminder", - DueTime: "0s", - Period: "2s", // Every 2 seconds, decay health - Data: []byte(`"Health decay reminder"`), + err = myActor.StartReminder(ctx, &api.ReminderRequest{ + ReminderName: "healthDecayReminder", + Period: "2s", // Every 2 seconds, decay health + DueTime: "0s", + Data: `"Health decay reminder"`, }) if err != nil { - log.Printf("failed to start health decay reminder: %w", err) + // The first reminder registrations have to succeed, + // if they don't, the app is not testing what we need to test and we need to exit + log.Fatalf("failed to start health decay reminder: %w", err) } - - go func(ctx context.Context) { - for { - select { - case <-ctx.Done(): - return - case <-deathSignal: - log.Println("Player is dead. Unregistering reminders...") - - log.Println("Unregistering health increase reminder for actor...") - unregIncReminderCtx, unregIncReminderCancel := context.WithTimeout(ctx, 5*time.Second) - err = client.UnregisterActorReminder(unregIncReminderCtx, &dapr.UnregisterActorReminderRequest{ - ActorType: "playerActorType", - ActorID: actorID, - Name: "healthReminder", - }) - unregIncReminderCancel() - if err != nil { - log.Printf("error unregistering actor reminder: %v", err) - } - - log.Println("Unregistering health decay reminder for actor...") - unregDecReminderCtx, unregDecReminderCancel := context.WithTimeout(ctx, 5*time.Second) - err = client.UnregisterActorReminder(unregDecReminderCtx, &dapr.UnregisterActorReminderRequest{ - ActorType: "playerActorType", - ActorID: actorID, - Name: "healthDecayReminder", - }) - unregDecReminderCancel() - if err != nil { - log.Printf("error unregistering actor reminder: %v", err) - } - - log.Println("Player reminders unregistered. Reviving player...") - req := &dapr.InvokeActorRequest{ - ActorType: "playerActorType", - ActorID: actorID, - Method: "RevivePlayer", - Data: []byte(`"player-1"`), - } - invokeCtx, invokeCancel := context.WithTimeout(ctx, 5*time.Second) - _, err = client.InvokeActor(invokeCtx, req) - invokeCancel() - if err != nil { - log.Printf("error invoking actor method RevivePlayer: %v", err) - } - log.Println("Player revived, health reset to 100. Restarting reminders...") - - incRemCtx, incRemCancel := context.WithTimeout(ctx, 5*time.Second) - // Restart reminders - err = client.RegisterActorReminder(incRemCtx, &dapr.RegisterActorReminderRequest{ - ActorType: "playerActorType", - ActorID: actorID, - Name: "healthReminder", - DueTime: "10s", - Period: "20s", - Data: []byte(`"Health increase reminder"`), - }) - incRemCancel() - if err != nil { - log.Printf("error starting actor reminder: %v", err) - } - log.Println("Started health increase reminder for actor:", actorID) - decRemCtx, decRemCancel := context.WithTimeout(ctx, 5*time.Second) - err = client.RegisterActorReminder(decRemCtx, &dapr.RegisterActorReminderRequest{ - ActorType: "playerActorType", - ActorID: actorID, - Name: "healthDecayReminder", - DueTime: "0s", - Period: "2s", // Every 5 seconds, decay health - Data: []byte(`"Health decay reminder"`), - }) - decRemCancel() - if err != nil { - log.Printf("error starting health decay reminder: %v", err) - } - log.Println("Started health decay reminder for actor:", actorID) - } - } - }(ctx) + defer stopReminder(ctx, myActor, "healthDecayReminder") + log.Println("Started healthDecayReminder for actor:", myActor.ID()) // Graceful shutdown on Ctrl+C or SIGTERM (for Docker/K8s graceful shutdown) signalChan := make(chan os.Signal, 1) @@ -151,42 +74,24 @@ func main() { // monitorPlayerHealth continuously checks the player's health every 5 seconds // and signals via a channel if the player is dead (health <= 0). -func monitorPlayerHealth(ctx context.Context, client dapr.Client, actorID string, deathSignal chan bool) { +func monitorPlayerHealth(ctx context.Context, actor *api.ClientStub) { for { select { case <-ctx.Done(): return default: // Check actor player's health - getPlayerRequest := &api.GetPlayerRequest{ActorID: actorID} - requestData, err := json.Marshal(getPlayerRequest) - if err != nil { - log.Printf("error marshaling request data: %v", err) - } - - req := &dapr.InvokeActorRequest{ - ActorType: "playerActorType", - ActorID: actorID, - Method: "GetUser", - Data: requestData, - } - invokeCtx, invokeCancel := context.WithTimeout(ctx, 5*time.Second) - resp, err := client.InvokeActor(invokeCtx, req) - invokeCancel() + playerResp, err := actor.GetUser(ctx) if err != nil { log.Printf("error invoking actor method GetUser: %v", err) + continue } - playerResp := &api.GetPlayerResponse{} - err = json.Unmarshal(resp.Data, playerResp) - if err != nil { - log.Printf("error unmarshaling player state: %v", err) - } log.Printf("Player health: %v\n", playerResp.Health) // If health is zero or below, signal player death if playerResp.Health <= 0 { - deathSignal <- true + revivePlayer(ctx, actor) } else { log.Printf("Player is alive with health: %d\n", playerResp.Health) } @@ -196,3 +101,50 @@ func monitorPlayerHealth(ctx context.Context, client dapr.Client, actorID string } } } + +func revivePlayer(ctx context.Context, actor *api.ClientStub) { + log.Println("Player is dead. Unregistering reminders...") + + stopReminder(ctx, actor, "healthReminder") + stopReminder(ctx, actor, "healthDecayReminder") + + log.Println("Player reminders unregistered. Reviving player...") + err := actor.RevivePlayer(ctx, "player-1") + if err != nil { + log.Printf("error invoking actor method RevivePlayer: %v", err) + } + log.Println("Player revived, health reset to 100. Restarting reminders...") + + // Restart reminders + err = actor.StartReminder(ctx, &api.ReminderRequest{ + ReminderName: "healthReminder", + Period: "20s", + DueTime: "10s", + Data: `"Health increase reminder"`, + }) + if err != nil { + log.Printf("error starting actor reminder: %v", err) + } + log.Println("Started health increase reminder for actor:", actor.ID()) + + err = actor.StartReminder(ctx, &api.ReminderRequest{ + ReminderName: "healthDecayReminder", + Period: "2s", + DueTime: "0s", + Data: `"Health decay reminder"`, + }) + if err != nil { + log.Printf("error starting health decay reminder: %v", err) + } + log.Println("Started health decay reminder for actor:", actor.ID()) +} + +func stopReminder(ctx context.Context, myActor *api.ClientStub, reminderName string) { + log.Printf("Unregistering '%s' reminder for actor...", reminderName) + err := myActor.StopReminder(ctx, &api.ReminderRequest{ + ReminderName: reminderName, + }) + if err != nil { + log.Printf("error unregistering actor reminder '%s': %v", reminderName, err) + } +} diff --git a/scheduler-actor-reminders/go.mod b/scheduler-actor-reminders/go.mod index e958c928..e66d3b56 100644 --- a/scheduler-actor-reminders/go.mod +++ b/scheduler-actor-reminders/go.mod @@ -2,15 +2,13 @@ module test-infra/scheduler-actor-reminders go 1.23.1 -require ( - github.com/dapr/go-sdk v1.11.0 - github.com/dapr/go-sdk/examples/actor v0.0.0-20240626135542-c417f950fe1d -) +require github.com/dapr/go-sdk v1.11.0 require ( github.com/dapr/dapr v1.14.0 // indirect github.com/go-chi/chi/v5 v5.1.0 // indirect github.com/google/uuid v1.6.0 // indirect + github.com/kr/pretty v0.3.1 // indirect github.com/rogpeppe/go-internal v1.13.1 // indirect go.opentelemetry.io/otel v1.27.0 // indirect golang.org/x/net v0.26.0 // indirect diff --git a/scheduler-actor-reminders/go.sum b/scheduler-actor-reminders/go.sum index 53e70fb9..6d48567d 100644 --- a/scheduler-actor-reminders/go.sum +++ b/scheduler-actor-reminders/go.sum @@ -1,9 +1,8 @@ +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/dapr/dapr v1.14.0 h1:SIQsNX1kH31JRDIS4k8IZ6eomM/BAcOP844PhQIT+BQ= github.com/dapr/dapr v1.14.0/go.mod h1:oDNgaPHQIDZ3G4n4g89TElXWgkluYwcar41DI/oF4gw= github.com/dapr/go-sdk v1.11.0 h1:clANpOQd6MsfvSa6snaX8MVk6eRx26Vsj5GxGdQ6mpE= github.com/dapr/go-sdk v1.11.0/go.mod h1:btZ/tX8eYnx0fg3HiJUku8J5QBRXHsp3kAB1BUiTxXY= -github.com/dapr/go-sdk/examples/actor v0.0.0-20240626135542-c417f950fe1d h1:J7u+R5/FXuh1y757FHAnA75g3w8ADzm4idO1kQmKH80= -github.com/dapr/go-sdk/examples/actor v0.0.0-20240626135542-c417f950fe1d/go.mod h1:Vg/MQZ6O3JVTtp6NgNtAGRmyCamXjZDp6OBqkLfF8W8= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw= @@ -18,8 +17,10 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= diff --git a/scheduler-actor-reminders/server/player-actor-server.go b/scheduler-actor-reminders/server/player-actor-server.go new file mode 100644 index 00000000..ba7c06d2 --- /dev/null +++ b/scheduler-actor-reminders/server/player-actor-server.go @@ -0,0 +1,60 @@ +package main + +import ( + "context" + "log" + http2 "net/http" + "os" + "os/signal" + "syscall" + + "github.com/dapr/go-sdk/actor" + dapr "github.com/dapr/go-sdk/client" + "github.com/dapr/go-sdk/service/http" +) + +const appPort = ":3007" + +func playerActorFactory() actor.ServerContext { + client, err := dapr.NewClient() + if err != nil { + panic(err) + } + + return &PlayerActor{ + DaprClient: client, + Health: 100, // initial health + } +} + +func main() { + _, cancel := context.WithCancel(context.Background()) + defer cancel() + + daprService := http.NewService(appPort) + // Register actor factory, meaning register actor methods to be called by client + daprService.RegisterActorImplFactoryContext(playerActorFactory) + + go func() { + log.Println("Starting Dapr actor runtime...") + if err := daprService.Start(); err != nil && err.Error() != http2.ErrServerClosed.Error() { + log.Fatalf("error starting Dapr actor runtime: %v", err) + } + }() + + waitForShutdown(cancel) + if err := daprService.GracefulStop(); err != nil { + log.Fatalf("error stopping Dapr actor runtime: %v", err) + } +} + +// waitForShutdown keeps the app alive until an interrupt or termination signal is received +func waitForShutdown(cancelFunc context.CancelFunc) { + sigCh := make(chan os.Signal, 1) + // Notify the channel on Interrupt (Ctrl+C) or SIGTERM (for Docker/K8s graceful shutdown) + signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM) + <-sigCh + + log.Println("Shutting down...") + cancelFunc() +} diff --git a/scheduler-actor-reminders/server/player-actor.go b/scheduler-actor-reminders/server/player-actor.go index 52869686..24288f6d 100644 --- a/scheduler-actor-reminders/server/player-actor.go +++ b/scheduler-actor-reminders/server/player-actor.go @@ -3,60 +3,101 @@ package main import ( "context" "log" - http2 "net/http" - "os" - "os/signal" - "syscall" "test-infra/scheduler-actor-reminders/api" "github.com/dapr/go-sdk/actor" dapr "github.com/dapr/go-sdk/client" - "github.com/dapr/go-sdk/service/http" ) -const appPort = ":3007" +const stateKey = "health" -func playerActorFactory() actor.ServerContext { - client, err := dapr.NewClient() - if err != nil { - panic(err) - } - - return &api.PlayerActor{ - DaprClient: client, - Health: 100, // initial health - } +type PlayerActor struct { + actor.ServerImplBaseCtx + DaprClient dapr.Client + Health int } -func main() { - _, cancel := context.WithCancel(context.Background()) - defer cancel() +func (p *PlayerActor) Type() string { + return api.PlayerActorType +} - daprService := http.NewService(appPort) - // Register actor factory, meaning register actor methods to be called by client - daprService.RegisterActorImplFactoryContext(playerActorFactory) +// GetUser retrieving the state of the PlayerActor +func (p *PlayerActor) GetUser(ctx context.Context) (*api.GetPlayerResponse, error) { + log.Printf("Player Actor ID: %s has a health level of: %d\n", p.ID(), p.Health) + return &api.GetPlayerResponse{ + ActorID: p.ID(), + Health: p.Health, + }, nil +} - go func() { - log.Println("Starting Dapr actor runtime...") - if err := daprService.Start(); err != nil && err.Error() != http2.ErrServerClosed.Error() { - log.Fatalf("error starting Dapr actor runtime: %v", err) - } - }() +// Invoke invokes an action on the actor +func (p *PlayerActor) Invoke(ctx context.Context, req string) (string, error) { + log.Println("get req = ", req) + return req, nil +} - waitForShutdown(cancel) - if err := daprService.GracefulStop(); err != nil { - log.Fatalf("error stopping Dapr actor runtime: %v", err) +// RevivePlayer revives the actor players health back to 100 +func (p *PlayerActor) RevivePlayer(ctx context.Context, id string) error { + log.Printf("Reviving player: %s\n", p.ID()) + p.Health = 100 + if err := p.GetStateManager().Set(ctx, stateKey, p.Health); err != nil { + log.Printf("error saving state: %v", err) } + + return nil } -// waitForShutdown keeps the app alive until an interrupt or termination signal is received -func waitForShutdown(cancelFunc context.CancelFunc) { - sigCh := make(chan os.Signal, 1) - // Notify the channel on Interrupt (Ctrl+C) or SIGTERM (for Docker/K8s graceful shutdown) - signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM) - <-sigCh +// StartReminder registers a reminder for the actor +func (p *PlayerActor) StartReminder(ctx context.Context, req *api.ReminderRequest) error { + log.Println("Starting reminder:", req.ReminderName) + return p.DaprClient.RegisterActorReminder(ctx, &dapr.RegisterActorReminderRequest{ + ActorType: p.Type(), + ActorID: p.ID(), + Name: req.ReminderName, + DueTime: req.DueTime, + Period: req.Period, + Data: []byte(req.Data), + }) +} + +// StopReminder unregisters a reminder for the actor +func (p *PlayerActor) StopReminder(ctx context.Context, req *api.ReminderRequest) error { + log.Println("Stopping reminder:", req.ReminderName) + return p.DaprClient.UnregisterActorReminder(ctx, &dapr.UnregisterActorReminderRequest{ + ActorType: p.Type(), + ActorID: p.ID(), + Name: req.ReminderName, + }) +} - log.Println("Shutting down...") - cancelFunc() +// ReminderCall executes logic to handle what happens when the reminder is triggered +// Dapr automatically calls this method when a reminder fires for the player actor +func (p *PlayerActor) ReminderCall(reminderName string, state []byte, dueTime string, period string) { + log.Println("received reminder = ", reminderName, " state = ", string(state), "duetime = ", dueTime, "period = ", period) + switch reminderName { + case "healthReminder": + if p.Health < 100 { + p.Health += 10 + if p.Health > 100 { + p.Health = 100 + } + log.Printf("Player Actor health increased. Current health: %d\n", p.Health) + } + case "healthDecayReminder": + p.Health -= 5 + if p.Health < 0 { + log.Println("Player Actor died...") + } + log.Printf("Health decreased. Current health: %d\n", p.Health) + default: + log.Printf("Unknown reminder: %s\n", reminderName) + return + } + + // Update the state of the actor + err := p.GetStateManager().Set(context.TODO(), stateKey, p.Health) + if err != nil { + log.Printf("error saving state: %v", err) + } }