example_struct_actor_test.go
1 package actor_test 2 3 import ( 4 "context" 5 "fmt" 6 "time" 7 8 "github.com/lightningnetwork/lnd/actor" 9 "github.com/lightningnetwork/lnd/fn/v2" 10 ) 11 12 // CounterMsg is a message type for the stateful counter actor. 13 // It can be used to increment the counter or get its current value. 14 type CounterMsg struct { 15 actor.BaseMessage 16 Increment int 17 GetValue bool 18 Who string 19 } 20 21 // MessageType implements actor.Message. 22 func (m CounterMsg) MessageType() string { return "CounterMsg" } 23 24 // CounterResponse is a response type for the counter actor. 25 type CounterResponse struct { 26 Value int 27 Responder string 28 } 29 30 // StatefulCounterActor demonstrates an actor that maintains internal state (a 31 // counter) and processes messages to modify or query that state. 32 type StatefulCounterActor struct { 33 counter int 34 actorID string 35 } 36 37 // NewStatefulCounterActor creates a new counter actor. 38 func NewStatefulCounterActor(id string) *StatefulCounterActor { 39 return &StatefulCounterActor{ 40 actorID: id, 41 } 42 } 43 44 // Receive is the message handler for the StatefulCounterActor. 45 // It implements the actor.ActorBehavior interface implicitly when wrapped. 46 func (s *StatefulCounterActor) Receive(ctx context.Context, 47 msg CounterMsg) fn.Result[CounterResponse] { 48 49 if msg.Increment > 0 { 50 // For increment, we can just acknowledge or return the new 51 // value. Messages are sent serially, so we don't need to worry 52 // about a mutex here. 53 s.counter += msg.Increment 54 55 return fn.Ok(CounterResponse{ 56 Value: s.counter, 57 Responder: s.actorID, 58 }) 59 } 60 61 if msg.GetValue { 62 return fn.Ok(CounterResponse{ 63 Value: s.counter, 64 Responder: s.actorID, 65 }) 66 } 67 68 return fn.Err[CounterResponse](fmt.Errorf("invalid CounterMsg")) 69 } 70 71 // ExampleActor_stateful demonstrates creating an actor whose behavior is defined 72 // by a struct with methods, allowing it to maintain internal state. 73 func ExampleActor_stateful() { 74 system := actor.NewActorSystem() 75 defer system.Shutdown() 76 77 counterServiceKey := actor.NewServiceKey[CounterMsg, CounterResponse]( 78 "struct-counter-service", 79 ) 80 81 // Create an instance of our stateful actor logic. 82 actorID := "counter-actor-1" 83 counterLogic := NewStatefulCounterActor(actorID) 84 85 // Spawn the actor. 86 // The counterLogic instance itself satisfies the ActorBehavior 87 // interface because its Receive method matches the required signature. 88 counterRef, err := counterServiceKey.Spawn( 89 system, actorID, counterLogic, 90 ) 91 if err != nil { 92 fmt.Printf("Failed to spawn actor: %v\n", err) 93 return 94 } 95 fmt.Printf("Actor %s spawned.\n", counterRef.ID()) 96 97 // Send messages to increment the counter. 98 for i := 1; i <= 3; i++ { 99 askCtx, askCancel := context.WithTimeout( 100 context.Background(), 1*time.Second, 101 ) 102 futureResp := counterRef.Ask(askCtx, 103 CounterMsg{ 104 Increment: i, 105 Who: fmt.Sprintf("Incrementer-%d", i), 106 }, 107 ) 108 awaitCtx, awaitCancel := context.WithTimeout( 109 context.Background(), 1*time.Second, 110 ) 111 resp := futureResp.Await(awaitCtx) 112 113 resp.WhenOk(func(r CounterResponse) { 114 fmt.Printf("Incremented by %d, new value: %d "+ 115 "(from %s)\n", i, r.Value, r.Responder) 116 }) 117 resp.WhenErr(func(e error) { 118 fmt.Printf("Error incrementing: %v\n", e) 119 }) 120 awaitCancel() 121 askCancel() 122 } 123 124 // Send a message to get the current value. 125 askCtx, askCancel := context.WithTimeout( 126 context.Background(), 1*time.Second, 127 ) 128 futureResp := counterRef.Ask( 129 askCtx, CounterMsg{GetValue: true, Who: "Getter"}, 130 ) 131 132 awaitCtx, awaitCancel := context.WithTimeout( 133 context.Background(), 1*time.Second, 134 ) 135 136 finalValueResp := futureResp.Await(awaitCtx) 137 finalValueResp.WhenOk(func(r CounterResponse) { 138 fmt.Printf("Final counter value: %d (from %s)\n", 139 r.Value, r.Responder) 140 }) 141 finalValueResp.WhenErr(func(e error) { 142 fmt.Printf("Error getting value: %v\n", e) 143 }) 144 awaitCancel() 145 askCancel() 146 147 // Output: 148 // Actor counter-actor-1 spawned. 149 // Incremented by 1, new value: 1 (from counter-actor-1) 150 // Incremented by 2, new value: 3 (from counter-actor-1) 151 // Incremented by 3, new value: 6 (from counter-actor-1) 152 // Final counter value: 6 (from counter-actor-1) 153 }