Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

react immediately to wakeups #1601

Closed
wants to merge 11 commits into from
2 changes: 1 addition & 1 deletion data/scenarios/Challenges/Sokoban/_foresight/solution.sw
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ def firstLeg =
pushUntilBarrier;

wait 4;
move;
moveUntilBlocked;
doN 5 (turn left; moveUntilBlocked);

turn right;
Expand Down
1 change: 0 additions & 1 deletion data/scenarios/Challenges/_combo-lock/solution.sw
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ def moveToLock =
end;

def cycleCombos = \n.
wait 1;
entityNorth <- scan north;
let hasGate = case entityNorth (\_. false) (\x. x == "gate") in
if hasGate {
Expand Down
3 changes: 3 additions & 0 deletions data/scenarios/Testing/00-ORDER.txt
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ Achievements
1341-command-count.yaml
1355-combustion.yaml
1379-single-world-portal-reorientation.yaml
1322-wait-with-instant.yaml
1598-detect-entity-change.yaml
1598-watch-wait-instant.yaml
1399-backup-command.yaml
1430-built-robot-ownership.yaml
1536-custom-unwalkable-entities.yaml
Expand Down
72 changes: 72 additions & 0 deletions data/scenarios/Testing/1322-wait-with-instant.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
version: 1
name: Using wait with instant
author: Karl Ostmo
description: |
Observe timing of (instant $ wait 1)
interspersed with other commands
creative: false
seed: 0
objectives:
- goal:
- |
Hare must win by three cells
condition: |
h <- robotnamed "hare";
hareloc <- as h {whereami};

t <- robotnamed "tortoise";
tortoiseloc <- as t {whereami};

let xDiff = fst hareloc - fst tortoiseloc in

return $ fst hareloc == 0 && xDiff == 3;
solution: |
noop;
robots:
- name: base
dir: [1, 0]
display:
invisible: true
devices:
- hourglass
- logger
- name: tortoise
system: true
display:
invisible: false
attr: green
dir: [1, 0]
program: |
move; move;
move; move;
move; move;
- name: hare
system: true
display:
invisible: false
attr: snow
dir: [1, 0]
program: |
instant (
move; move;
wait 1;
move; move;
wait 1;
move; move;
);
world:
dsl: |
{blank}
upperleft: [-6, 2]
offset: false
palette:
'.': [grass, erase]
'd': [dirt, erase]
'B': [grass, erase, base]
'T': [grass, erase, tortoise]
'H': [grass, erase, hare]
map: |
B.....d.
T.....d.
H.....d.
......d.
95 changes: 95 additions & 0 deletions data/scenarios/Testing/1598-detect-entity-change.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
version: 1
name: Entity change detection
author: Karl Ostmo
description: |
Ensure that a change to an entity can be observed
by a system robot within a single tick.

In this scenario, the base will first `swap` the
existing `dial (R)`{=entity} with a `dial (G)`{=entity},
then immediately `swap` again with a `dial (B)`{=entity}.

The system robot should be able to detect the presence
of the `dial (G)`{=entity} before it is `swap`ped a second time.
creative: true
seed: 0
objectives:
- goal:
- |
Turn the light green
condition: |
as base {has "flower"};
prerequisite:
not: blue_light
- id: blue_light
teaser: No blue light
optional: true
goal:
- |
Turn the light blue
condition: |
r <- robotnamed "lockbot";
as r {ishere "dial (B)"};
robots:
- name: base
dir: [1, 0]
display:
invisible: true
devices:
- hourglass
- fast grabber
- logger
- treads
inventory:
- [1, "dial (R)"]
- [1, "dial (G)"]
- [1, "dial (B)"]
- name: lockbot
system: true
display:
invisible: true
dir: [1, 0]
program: |
run "scenarios/Testing/_1598-detect-entity-change/setup.sw"
inventory:
- [1, flower]
solution: |
wait 2;
move;
move;
swap "dial (G)";
//wait 0;
swap "dial (B)";
entities:
- name: "dial (R)"
display:
char: '•'
attr: red
description:
- A red dial
properties: [known, pickable]
- name: "dial (G)"
display:
char: '•'
attr: green
description:
- A green dial
properties: [known, pickable]
- name: "dial (B)"
display:
char: '•'
attr: blue
description:
- A blue dial
properties: [known, pickable]
world:
dsl: |
{blank}
upperleft: [-1, -1]
offset: false
palette:
'.': [grass, erase]
'B': [grass, erase, base]
'c': [grass, dial (R), lockbot]
map: |
B.c
61 changes: 61 additions & 0 deletions data/scenarios/Testing/1598-watch-wait-instant.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
version: 1
name: Watch and Wait with Instant
author: Karl Ostmo
description: |
Ensure that the `watch` command does
not introduce a tick delay.
creative: false
seed: 0
objectives:
- teaser: Has item
goal:
- |
Observe item
condition: |
r <- robotnamed "watchbot";
as r {ishere "rock"};
//as r {return false};
robots:
- name: base
dir: [1, 0]
devices:
- hourglass
- fast grabber
- logger
- treads
inventory:
- [1, flower]
- [1, tree]
- [1, rock]
- name: watchbot
system: true
display:
invisible: false
dir: [1, 0]
program: |
def watchAndWait =
watch down;
wait 1000;
watchAndWait;
end;

instant watchAndWait;
inventory:
- [1, flower]
solution: |
move; move;
swap "flower";
swap "tree";
swap "rock";
known: [gold]
world:
dsl: |
{blank}
upperleft: [-1, -1]
offset: false
palette:
'.': [grass, erase]
'B': [grass, erase, base]
'c': [grass, gold, watchbot]
map: |
B.c
17 changes: 17 additions & 0 deletions data/scenarios/Testing/_1598-detect-entity-change/setup.sw
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
def doUntilCorrect =
herenow <- ishere "dial (G)";
if herenow {
give base "flower";
} {
watch down;
wait 1000;
doUntilCorrect;
}
end;

def go =
instant $
doUntilCorrect;
end;

go;
54 changes: 39 additions & 15 deletions src/swarm-engine/Swarm/Game/State/Robot.hs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ import Data.Maybe (fromMaybe, mapMaybe)
import Data.Set qualified as S
import Data.Tuple (swap)
import GHC.Generics (Generic)
import Swarm.Game.CESK (CESK (Waiting), TickNumber (..), addTicks)
import Swarm.Game.CESK (CESK (Waiting), TickNumber (..))
import Swarm.Game.Location
import Swarm.Game.ResourceLoading (NameGenerator)
import Swarm.Game.Robot
Expand Down Expand Up @@ -296,22 +296,45 @@ activateRobot rid = internalActiveRobots %= IS.insert rid
-- | Removes robots whose wake up time matches the current game ticks count
-- from the 'waitingRobots' queue and put them back in the 'activeRobots' set
-- if they still exist in the keys of 'robotMap'.
wakeUpRobotsDoneSleeping :: (Has (State Robots) sig m) => TickNumber -> m ()
--
-- = Mutations
--
-- This function modifies:
--
-- * 'wakeLog'
-- * 'robotsWatching' (by way of 'clearWatchingRobots')
-- * 'internalWaitingRobots'
-- * 'internalActiveRobots' (aka 'activeRobots')
--
-- = Efficiency (excluding 'wakeLog')
--
-- * O(log N) where N is the number of keys in 'waitingMap'
-- * O(M) where M is the number of robots to wake
-- * O(M + A) where A is the number of active robots
wakeUpRobotsDoneSleeping :: (Has (State Robots) sig m) => TickNumber -> m IntSet
wakeUpRobotsDoneSleeping time = do
mrids <- internalWaitingRobots . at time <<.= Nothing
case mrids of
Nothing -> return ()
Just rids -> do
robots <- use robotMap
let aliveRids = filter (`IM.member` robots) rids
internalActiveRobots %= IS.union (IS.fromList aliveRids)
waitingMap <- use waitingRobots

let (beforeMap, maybeAt, futureMap) = M.splitLookup time waitingMap
currentTickMap = maybe mempty (M.singleton time) maybeAt
-- Note: 'union' is safe because currentTickMap and beforeMap are disjoint
wakeableRIDs = concat $ M.elems $ M.union currentTickMap beforeMap

-- These robots' wake times may have been moved "forward"
-- by 'wakeWatchingRobots'.
clearWatchingRobots wakeableRIDs

robots <- use robotMap
-- Limit ourselves to the robots that have not expired in their sleep
let newlyAlive = filter (`IM.member` robots) wakeableRIDs

forM_ newlyAlive $ \rid ->
wakeLog %= (WakeLogEvent rid time DoneSleeping :)

forM_ aliveRids $ \rid ->
wakeLog %= (WakeLogEvent rid time DoneSleeping :)
internalWaitingRobots .= futureMap

-- These robots' wake times may have been moved "forward"
-- by 'wakeWatchingRobots'.
clearWatchingRobots rids
internalActiveRobots %= IS.union (IS.fromList newlyAlive)
use activeRobots

-- | Clear the "watch" state of all of the
-- awakened robots
Expand Down Expand Up @@ -363,7 +386,8 @@ wakeWatchingRobots currentTick loc = do

-- Step 4: Re-add the watching bots to be awakened at the next tick:
wakeableBotIds = map fst wakeTimes
newWakeTime = addTicks 1 currentTick
-- newWakeTime = addTicks 1 currentTick
newWakeTime = currentTick
newInsertions = M.singleton newWakeTime wakeableBotIds

forM_ wakeableBotIds $ \rid ->
Expand Down
Loading