From 244f0bb58815ad23ffd777300c04c2fd440aa8c1 Mon Sep 17 00:00:00 2001 From: Anthony De Meulemeester Date: Fri, 17 Nov 2023 10:15:58 +0100 Subject: [PATCH] WIP: Redis persistance example (#35) --- examples/persistance/main.go | 153 +++++++++++++++++++++++++++++++++++ go.mod | 2 + go.sum | 4 + 3 files changed, 159 insertions(+) create mode 100644 examples/persistance/main.go diff --git a/examples/persistance/main.go b/examples/persistance/main.go new file mode 100644 index 0000000..13cde7b --- /dev/null +++ b/examples/persistance/main.go @@ -0,0 +1,153 @@ +package main + +import ( + "context" + "encoding/json" + "fmt" + "log" + "sync" + "time" + + "github.com/anthdm/hollywood/actor" + "github.com/redis/go-redis/v9" +) + +type Storer interface { + Store(key string, data []byte) error + Load(key string) ([]byte, error) +} + +func WithPersistance(store Storer) func(actor.ReceiveFunc) actor.ReceiveFunc { + return func(next actor.ReceiveFunc) actor.ReceiveFunc { + return func(c *actor.Context) { + switch c.Message().(type) { + case actor.Initialized: + p, ok := c.Receiver().(Persister) + if !ok { + next(c) + return + } + b, err := store.Load(c.PID().String()) + if err != nil { + fmt.Println(err) + next(c) + return + } + var data map[string]any + if err := json.Unmarshal(b, &data); err != nil { + log.Fatal(err) + } + if err := p.LoadState(data); err != nil { + fmt.Println("load state error:", err) + next(c) + } + case actor.Stopped: + if p, ok := c.Receiver().(Persister); ok { + s, err := p.State() + if err != nil { + fmt.Println("could not get state", err) + next(c) + return + } + if err := store.Store(c.PID().String(), s); err != nil { + fmt.Println("failed to store the state", err) + next(c) + return + } + } + } + next(c) + } + } +} + +type Persister interface { + State() ([]byte, error) + LoadState(map[string]any) error +} + +type State struct { + Health int `json:"health"` + Username string `json:"username"` +} + +type PlayerState struct { + Health int + Username string +} + +type TakeDamage struct { + Amount int +} + +func (p *PlayerState) Receive(c *actor.Context) { + switch msg := c.Message().(type) { + case actor.Started: + case actor.Stopped: + case TakeDamage: + p.Health -= msg.Amount + fmt.Println("took damage, health ", p.Health) + } +} + +func newPlayerState(health int, username string) actor.Producer { + return func() actor.Receiver { + return &PlayerState{ + Health: health, + Username: username, + } + } +} + +func (p *PlayerState) LoadState(data map[string]any) error { + fmt.Println("player loading state", data) + p.Health = int(data["health"].(float64)) + p.Username = data["username"].(string) + return nil +} + +func (p *PlayerState) State() ([]byte, error) { + state := State{ + Username: p.Username, + Health: p.Health, + } + return json.Marshal(state) +} + +type RedisStore struct { + client *redis.Client +} + +func newRedisStore(c *redis.Client) *RedisStore { + return &RedisStore{ + client: c, + } +} + +func (r *RedisStore) Store(key string, state []byte) error { + return r.client.Set(context.TODO(), key, state, 0).Err() +} + +func (r *RedisStore) Load(key string) ([]byte, error) { + val, err := r.client.Get(context.TODO(), key).Result() + return []byte(val), err +} + +func main() { + var ( + e = actor.NewEngine() + redisClient = redis.NewClient(&redis.Options{ + Addr: "localhost:6379", + Password: "", // no password set + DB: 0, // use default DB + }) + store = newRedisStore(redisClient) + pid = e.Spawn(newPlayerState(100, "James"), "playerState", actor.WithMiddleware(WithPersistance(store))) + ) + time.Sleep(time.Second * 1) + e.Send(pid, TakeDamage{Amount: 9}) + time.Sleep(time.Second * 1) + wg := &sync.WaitGroup{} + e.Poison(pid, wg) + wg.Wait() +} diff --git a/go.mod b/go.mod index 35078f2..4618472 100644 --- a/go.mod +++ b/go.mod @@ -13,6 +13,7 @@ require ( require ( github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/klauspost/cpuid/v2 v2.0.9 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect @@ -20,6 +21,7 @@ require ( github.com/prometheus/client_model v0.3.0 // indirect github.com/prometheus/common v0.42.0 // indirect github.com/prometheus/procfs v0.9.0 // indirect + github.com/redis/go-redis/v9 v9.0.4 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect ) diff --git a/go.sum b/go.sum index c1fe271..9db8a91 100644 --- a/go.sum +++ b/go.sum @@ -6,6 +6,8 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= @@ -37,6 +39,8 @@ github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc= github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI= github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY= +github.com/redis/go-redis/v9 v9.0.4 h1:FC82T+CHJ/Q/PdyLW++GeCO+Ol59Y4T7R4jbgjvktgc= +github.com/redis/go-redis/v9 v9.0.4/go.mod h1:WqMKv5vnQbRuZstUwxQI195wHy+t4PuXDOjzMvcuQHk= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=