Mimic States#

Mimic states allow one actor to use another actor’s state in place of its own. If you have two “Worker” actors riding in a car, it is useful to have their location mimic that of the car, rather than write a task for the workers that does the same thing as the car.

Mimic states are activated and deactivated in a similar manner to other states, and they can mimic ActiveStates as well.

...
actor.activate_mimic_state(
    self_state="name of state on self",
    mimic_state="name of state to mirror",
    mimic_actor=actor_object_that_has_mimic_state,
    task=self,
)
...
actor.deactivate_mimic_state(
    self_state="name of state on self",
    task=self,
)
...

Here is a complete example that demonstrates how to use a mimic state:

 1class Car(UP.Actor):
 2    location = UP.CartesianLocationChangingState(recording=True)
 3    speed = UP.State(default=2.0)
 4    riders = UP.State(default_factory=list)
 5
 6
 7class Worker(UP.Actor):
 8    location = UP.State(valid_types=UP.CartesianLocation, recording=True)
 9    car = UP.State()
10
11
12class CarMove(UP.Task):
13    def task(self, *, actor: Car):
14        new = UP.CartesianLocation(10, 10)
15        dist = new - actor.location
16        time = dist / actor.speed
17        actor.activate_location_state(
18            state="location",
19            speed=actor.speed,
20            waypoints=[new],
21            task=self,
22        )
23        yield UP.Wait(time)
24        actor.deactivate_all_states(task=self)
25        actor.location
26        while actor.riders:
27            rider = actor.riders.pop()
28            rider.succeed_knowledge_event(name="ARRIVED", cause="here")
29
30
31class WorkerRide(UP.Task):
32    def task(self, *, actor: Worker):
33        car: Car = actor.car
34        actor.activate_mimic_state(
35            self_state="location",
36            mimic_state="location",
37            mimic_actor=car,
38            task=self,
39        )
40        evt = actor.create_knowledge_event(name="ARRIVED")
41        # NOT A REHEARSAL-SAFE THING TO DO:
42        # Better: use a store get/put for real interaction
43        car.riders.append(actor)
44        yield evt
45        print(f"{actor} got event: {evt.get_payload()}: {env.now:.2f}")
46        actor.deactivate_mimic_state(
47            self_state="location",
48            task=self,
49        )
50
51def location_ping(env, time, actors):
52    while True:
53        yield env.timeout(time)
54        for a in actors:
55            a.location
56
57with UP.EnvironmentContext() as env:
58    car = Car(name="Zaphod", location=UP.CartesianLocation(0,0), speed=2)
59    w1 = Worker(name="Arthur", car=car, location=UP.CartesianLocation(1,1))
60    w2 = Worker(name="Trillian", car=car, location=UP.CartesianLocation(1,2))
61
62    CarMove().run(actor=car)
63    WorkerRide().run(actor=w1)
64    WorkerRide().run(actor=w2)
65
66    proc = env.process(location_ping(env, 0.3, [car, w1, w2]))
67
68    env.run(until=8)
69    print()
70    print(w1.location)
71    print(w2.location)
72    print(car.location)
73    print()
74    for i in range(10):
75        t1, loc1 = w1._location_history[i]
76        tc, locc = car._location_history[i]
77        print(((t1 - tc), (loc1.x - locc.x), (loc1.y - locc.y)))
78
79>>> Worker: Trillian got event: {'cause': 'here'}: 7.07
80>>> Worker: Arthur got event: {'cause': 'here'}: 7.07
81>>>
82>>> CartesianLocation(x=10.0, y=10.0, z=0.0)
83>>> CartesianLocation(x=10.0, y=10.0, z=0.0)
84>>> CartesianLocation(x=10.0, y=10.0, z=0.0)
85>>>
86>>> (0.0, 1, 1)
87>>> (0.0, 0.0, 0.0)
88>>> (0.0, 0.0, 0.0)
89>>> (0.0, 0.0, 0.0)
90>>> (0.0, 0.0, 0.0)
91>>> (0.0, 0.0, 0.0)
92>>> (0.0, 0.0, 0.0)
93>>> (0.0, 0.0, 0.0)
94>>> (0.0, 0.0, 0.0)
95>>> (0.0, 0.0, 0.0)

Things to note:

  • Line 26: These riders are put there by another task. This is generally bad form, but works for our small example.

  • Line 34: We are making the worker’s CartesianLocation state match the car’s CartesianLocationChangingState.

    • Both can be set with/return a CartesianLocation, so this is OK.

    • If we had one State mimicking a LinearChangingState, that would also work since a State can take a floating point value.

    • States type-check under the hood, so you’ll get notified if a mimic doesn’t match.

  • Line 43: Here we warn again about how this is a bad idea in general.

  • Line 46: Deactivation is always needed.

    • As discussed in Rehearsal, these states are also deactivated on an interrupt.

No coordination between the actors has to occur for this to work. In this example, the car exiting is done to show the useful nature of mimic states, and to demonstrate other UPSTAGE features, such as create_knowledge_event and succeed_knowledge_event.

As long as the state values are compatible, mimic_state should make one get its value (when requested) from its mimic. If either is recording, that will cause the recording to affect both. Finally, if the actor whose state is being mimiced changes or is deleted, then the mimic state may have issues. This is not explicitly accounted for, and it is up to the user to make sure dependent actors handle those situations.