Skip to content

Commit

Permalink
Robot activity counts in F2 menu (#1484)
Browse files Browse the repository at this point in the history
Towards #1341.

    scripts/play.sh -i data/scenarios/Testing/1341-command-count.yaml --autoplay --speed 1

![image](https://github.com/swarm-game/swarm/assets/261693/f658bb9c-6bb8-494c-b204-6d5bb0106b92)
  • Loading branch information
kostmo committed Sep 7, 2023
1 parent deef46e commit 1aa92d0
Show file tree
Hide file tree
Showing 22 changed files with 606 additions and 94 deletions.
1 change: 1 addition & 0 deletions data/scenarios/Testing/00-ORDER.txt
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
1320-world-DSL
1356-portals
144-subworlds
1341-command-count.yaml
1355-combustion.yaml
1379-single-world-portal-reorientation.yaml
1399-backup-command.yaml
Expand Down
107 changes: 107 additions & 0 deletions data/scenarios/Testing/1341-command-count.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
version: 1
name: Count commands
creative: true
description: |
Count commands and demonstrate various "duty cycles"
with system robots. The four robots should have duty
cycles of 100%, 50%, 33%, and 25% based on the number
of ticks that they `wait`.
objectives:
- goal:
- |
`grab` the "tree".
condition: |
as base {has "tree"}
solution: |
move;
move;
wait 10;
x <- harvest;
if (x == "flower") {
turn right;
move;
grab;
} {
return "";
};
robots:
- name: base
dir: [1, 0]
devices:
- treads
- grabber
- harvester
- logger
- comparator
- hourglass
- branch predictor
- name: idler1
dir: [1, 0]
system: true
devices:
- dictionary
- strange loop
- hourglass
program: |
def go =
wait 1;
go;
end;
go;
- name: idler2
dir: [1, 0]
system: true
devices:
- dictionary
- strange loop
- hourglass
program: |
def go =
wait 2;
go;
end;
go;
- name: idler3
dir: [1, 0]
system: true
devices:
- dictionary
- strange loop
- hourglass
program: |
def go =
wait 3;
go;
end;
go;
- name: idler4
dir: [1, 0]
system: true
devices:
- dictionary
- strange loop
- hourglass
program: |
def go =
wait 4;
go;
end;
go;
known: [flower, tree]
world:
default: [blank]
palette:
'.': [grass]
'f': [grass, flower]
'T': [grass, tree]
'B': [grass, null, base]
'1': [grass, null, idler1]
'2': [grass, null, idler2]
'3': [grass, null, idler3]
'4': [grass, null, idler4]
upperleft: [-1, 1]
map: |
........1
.B.fff..2
...T....3
........4
4 changes: 4 additions & 0 deletions src/Swarm/Game/CESK.hs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ import Swarm.Language.Requirement (ReqCtx)
import Swarm.Language.Syntax
import Swarm.Language.Types
import Swarm.Language.Value as V
import Swarm.Util.WindowedCounter

-- | A newtype representing a count of ticks (typically since the
-- start of a game).
Expand All @@ -108,6 +109,9 @@ newtype TickNumber = TickNumber {getTickNumber :: Integer}
addTicks :: Integer -> TickNumber -> TickNumber
addTicks i (TickNumber n) = TickNumber $ n + i

instance Offsettable TickNumber where
offsetBy = addTicks . fromIntegral

instance Pretty TickNumber where
pretty (TickNumber i) = pretty i

Expand Down
133 changes: 90 additions & 43 deletions src/Swarm/Game/Robot.hs
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,13 @@ module Swarm.Game.Robot (
machine,
systemRobot,
selfDestruct,
tickSteps,
runningAtomic,
activityCounts,
tickStepBudget,
tangibleCommandCount,
commandsHistogram,
lifetimeStepCount,
activityWindow,

-- ** Creation & instantiation
mkRobot,
Expand All @@ -76,10 +81,11 @@ module Swarm.Game.Robot (
hearingDistance,
) where

import Control.Lens hiding (contains)
import Control.Lens hiding (Const, contains)
import Data.Aeson (FromJSON, ToJSON)
import Data.Hashable (hashWithSalt)
import Data.Kind qualified
import Data.Map (Map)
import Data.Maybe (fromMaybe, isNothing)
import Data.Sequence (Seq)
import Data.Sequence qualified as Seq
Expand All @@ -99,12 +105,13 @@ import Swarm.Game.Universe
import Swarm.Language.Capability (Capability)
import Swarm.Language.Context qualified as Ctx
import Swarm.Language.Requirement (ReqCtx)
import Swarm.Language.Syntax (Syntax)
import Swarm.Language.Syntax (Const, Syntax)
import Swarm.Language.Text.Markdown (Document)
import Swarm.Language.Typed (Typed (..))
import Swarm.Language.Types (TCtx)
import Swarm.Language.Value as V
import Swarm.Util.Lens (makeLensesExcluding)
import Swarm.Util.Lens (makeLensesExcluding, makeLensesNoSigs)
import Swarm.Util.WindowedCounter
import Swarm.Util.Yaml
import System.Clock (TimeSpec)

Expand Down Expand Up @@ -167,6 +174,72 @@ data RobotPhase
| -- | The robot record represents a concrete robot in the world.
ConcreteRobot

data ActivityCounts = ActivityCounts
{ _tickStepBudget :: Int
, _tangibleCommandCount :: Int
, _commandsHistogram :: Map Const Int
, _lifetimeStepCount :: Int
, _activityWindow :: WindowedCounter TickNumber
}
deriving (Eq, Show, Generic, FromJSON, ToJSON)

makeLensesNoSigs ''ActivityCounts

-- | A counter that is decremented upon each step of the robot within the
-- CESK machine. Initially set to 'robotStepsPerTick' at each new tick.
--
-- The need for 'tickStepBudget' is a bit technical, and I hope I can
-- eventually find a different, better way to accomplish it.
-- Ideally, we would want each robot to execute a single
-- /command/ at every game tick, so that /e.g./ two robots
-- executing @move;move;move@ and @repeat 3 move@ (given a
-- suitable definition of @repeat@) will move in lockstep.
-- However, the second robot actually has to do more computation
-- than the first (it has to look up the definition of @repeat@,
-- reduce its application to the number 3, etc.), so its CESK
-- machine will take more steps. It won't do to simply let each
-- robot run until executing a command---because robot programs
-- can involve arbitrary recursion, it is very easy to write a
-- program that evaluates forever without ever executing a
-- command, which in this scenario would completely freeze the
-- UI. (It also wouldn't help to ensure all programs are
-- terminating---it would still be possible to effectively do
-- the same thing by making a program that takes a very, very
-- long time to terminate.) So instead, we allocate each robot
-- a certain maximum number of computation steps per tick
-- (defined in 'Swarm.Game.Step.evalStepsPerTick'), and it
-- suspends computation when it either executes a command or
-- reaches the maximum number of steps, whichever comes first.
--
-- It seems like this really isn't something the robot should be
-- keeping track of itself, but that seemed the most technically
-- convenient way to do it at the time. The robot needs some
-- way to signal when it has executed a command, which it
-- currently does by setting tickStepBudget to zero. However, that
-- has the disadvantage that when tickStepBudget becomes zero, we
-- can't tell whether that happened because the robot ran out of
-- steps, or because it executed a command and set it to zero
-- manually.
--
-- Perhaps instead, each robot should keep a counter saying how
-- many commands it has executed. The loop stepping the robot
-- can tell when the counter increments.
tickStepBudget :: Lens' ActivityCounts Int

-- | Total number of tangible commands executed over robot's lifetime
tangibleCommandCount :: Lens' ActivityCounts Int

-- | Histogram of commands executed over robot's lifetime
commandsHistogram :: Lens' ActivityCounts (Map Const Int)

-- | Total number of CESK steps executed over robot's lifetime.
-- This could be thought of as "CPU cycles" consumed, and is labeled
-- as "cycles" in the F2 dialog in the UI.
lifetimeStepCount :: Lens' ActivityCounts Int

-- | Sliding window over a span of ticks indicating ratio of activity
activityWindow :: Lens' ActivityCounts (WindowedCounter TickNumber)

-- | With a robot template, we may or may not have a location. With a
-- concrete robot we must have a location.
type family RobotLocation (phase :: RobotPhase) :: Data.Kind.Type where
Expand Down Expand Up @@ -197,7 +270,7 @@ data RobotR (phase :: RobotPhase) = RobotR
, _machine :: CESK
, _systemRobot :: Bool
, _selfDestruct :: Bool
, _tickSteps :: Int
, _activityCounts :: ActivityCounts
, _runningAtomic :: Bool
, _robotCreatedAt :: TimeSpec
}
Expand Down Expand Up @@ -396,43 +469,8 @@ systemRobot :: Lens' Robot Bool
-- | Does this robot wish to self destruct?
selfDestruct :: Lens' Robot Bool

-- | The need for 'tickSteps' is a bit technical, and I hope I can
-- eventually find a different, better way to accomplish it.
-- Ideally, we would want each robot to execute a single
-- /command/ at every game tick, so that /e.g./ two robots
-- executing @move;move;move@ and @repeat 3 move@ (given a
-- suitable definition of @repeat@) will move in lockstep.
-- However, the second robot actually has to do more computation
-- than the first (it has to look up the definition of @repeat@,
-- reduce its application to the number 3, etc.), so its CESK
-- machine will take more steps. It won't do to simply let each
-- robot run until executing a command---because robot programs
-- can involve arbitrary recursion, it is very easy to write a
-- program that evaluates forever without ever executing a
-- command, which in this scenario would completely freeze the
-- UI. (It also wouldn't help to ensure all programs are
-- terminating---it would still be possible to effectively do
-- the same thing by making a program that takes a very, very
-- long time to terminate.) So instead, we allocate each robot
-- a certain maximum number of computation steps per tick
-- (defined in 'Swarm.Game.Step.evalStepsPerTick'), and it
-- suspends computation when it either executes a command or
-- reaches the maximum number of steps, whichever comes first.
--
-- It seems like this really isn't something the robot should be
-- keeping track of itself, but that seemed the most technically
-- convenient way to do it at the time. The robot needs some
-- way to signal when it has executed a command, which it
-- currently does by setting tickSteps to zero. However, that
-- has the disadvantage that when tickSteps becomes zero, we
-- can't tell whether that happened because the robot ran out of
-- steps, or because it executed a command and set it to zero
-- manually.
--
-- Perhaps instead, each robot should keep a counter saying how
-- many commands it has executed. The loop stepping the robot
-- can tell when the counter increments.
tickSteps :: Lens' Robot Int
-- | Diagnostic and operational tracking of CESK steps or other activity
activityCounts :: Lens' Robot ActivityCounts

-- | Is the robot currently running an atomic block?
runningAtomic :: Lens' Robot Bool
Expand Down Expand Up @@ -485,7 +523,16 @@ mkRobot rid pid name descr loc dir disp m devs inv sys heavy ts =
, _machine = m
, _systemRobot = sys
, _selfDestruct = False
, _tickSteps = 0
, _activityCounts =
ActivityCounts
{ _tickStepBudget = 0
, _tangibleCommandCount = 0
, _commandsHistogram = mempty
, _lifetimeStepCount = 0
, -- NOTE: This value was chosen experimentally.
-- TODO(#1341): Make this dynamic based on game speed.
_activityWindow = mkWindow 64
}
, _runningAtomic = False
}
where
Expand Down

0 comments on commit 1aa92d0

Please sign in to comment.