/ actor / example_struct_actor_test.go
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  }