• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Scala-Robotics-Simulator / PPS-22-srs / #310

18 Aug 2025 12:42PM UTC coverage: 61.478% (-15.7%) from 77.166%
#310

push

github

srs-mate
refactor: update RobotActionLogic to use robot parameter directly and enable debug mode

1 of 4 new or added lines in 2 files covered. (25.0%)

309 existing lines in 26 files now uncovered.

932 of 1516 relevant lines covered (61.48%)

8.82 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/main/scala/io/github/srs/controller/ControllerModule.scala
1
package io.github.srs.controller
2

3
import scala.concurrent.duration.{ FiniteDuration, MILLISECONDS }
4
import scala.language.postfixOps
5
import scala.concurrent.duration.DurationInt
6

7
import cats.effect.std.Queue
8
import cats.effect.{ Clock, IO }
9
import cats.syntax.all.*
10
import io.github.srs.model.*
11
import io.github.srs.model.SimulationConfig.SimulationStatus
12
import io.github.srs.model.UpdateLogic.*
13
import io.github.srs.model.entity.dynamicentity.Robot
14
import io.github.srs.model.entity.dynamicentity.sensor.Sensor.senseAll
15
import io.github.srs.model.logic.*
16
import io.github.srs.utils.SimulationDefaults.debugMode
17
import io.github.srs.utils.random.RNG
18
import io.github.srs.utils.random.RandomDSL.{ generate, shuffle }
19

20
/**
21
 * Module that defines the controller logic for the Scala Robotics Simulator.
22
 */
23
object ControllerModule:
UNCOV
24

×
25
  /**
26
   * Controller trait that defines the interface for the controller.
27
   *
28
   * @tparam S
29
   *   the type of the state, which must extend [[ModelModule.State]].
30
   */
31
  trait Controller[S <: ModelModule.State]:
UNCOV
32
    /**
×
33
     * Starts the controller with the initial state.
34
     *
35
     * @param initialState
36
     *   the initial state of the simulation.
37
     */
38
    def start(initialState: S): IO[Unit]
39

40
    /**
41
     * Runs the simulation loop, processing events from the queue and updating the state.
42
     *
43
     * @param s
44
     *   the current state of the simulation.
45
     * @param queue
46
     *   a concurrent queue that holds events to be processed.
47
     * @return
48
     *   an [[IO]] task that completes when the simulation loop ends.
49
     */
50
    def simulationLoop(s: S, queue: Queue[IO, Event]): IO[Unit]
51

52
  end Controller
53

54
  /**
55
   * Provider trait that defines the interface for providing a controller.
56
   *
57
   * @tparam S
58
   *   the type of the state, which must extend [[ModelModule.State]].
59
   */
60
  trait Provider[S <: ModelModule.State]:
61
    val controller: Controller[S]
62

63
  /**
64
   * Defines the dependencies required by the controller module. In particular, it requires a
65
   * [[io.github.srs.view.ViewModule.Provider]] and a [[io.github.srs.model.ModelModule.Provider]].
66
   */
67
  type Requirements[S <: ModelModule.State] =
68
    io.github.srs.view.ViewModule.Provider[S] & io.github.srs.model.ModelModule.Provider[S]
69

70
  /**
71
   * Component trait that defines the interface for creating a controller.
72
   *
73
   * @tparam S
74
   *   the type of the simulation state, which must extend [[ModelModule.State]].
75
   */
76
  trait Component[S <: ModelModule.State]:
UNCOV
77
    context: Requirements[S] =>
×
78

79
    object Controller:
UNCOV
80

×
81
      /**
82
       * Creates a controller instance.
83
       *
84
       * @return
85
       *   a [[Controller]] instance.
86
       */
87
      def apply()(using bundle: LogicsBundle[S]): Controller[S] = new ControllerImpl
UNCOV
88

×
89
      /**
90
       * Private controller implementation that delegates the simulation loop to the provided model and view.
91
       */
92
      private class ControllerImpl(using bundle: LogicsBundle[S]) extends Controller[S]:
UNCOV
93

×
94
        override def start(initialState: S): IO[Unit] =
UNCOV
95
          for
×
96
            queueSim <- Queue.unbounded[IO, Event]
×
UNCOV
97
            _ <- context.view.init(queueSim)
×
98
            _ <- runBehavior(queueSim, initialState)
99
            _ <- simulationLoop(initialState, queueSim)
100
          yield ()
101

×
102
        override def simulationLoop(s: S, queue: Queue[IO, Event]): IO[Unit] =
UNCOV
103
          def loop(state: S): IO[Unit] =
×
UNCOV
104
            for
×
UNCOV
105
              startTime <- Clock[IO].realTime.map(_.toMillis)
×
106
              _ <- runBehavior(queue, state).whenA(state.simulationStatus == SimulationStatus.RUNNING)
×
UNCOV
107

×
108
              events <- queue.tryTakeN(Some(50))
109
              shuffledEvents <- shuffleEvents(queue, state, events)
110
              newState <- handleEvents(state, shuffledEvents)
111

112
              _ <- context.view.render(newState)
113

114
              nextState <- nextStep(newState, startTime)
115

116
              stop = newState.simulationStatus == SimulationStatus.STOPPED ||
117
                newState.simulationTime.exists(max => newState.elapsedTime >= max)
118

119
              endTime <- Clock[IO].realTime.map(_.toMillis)
120
              _ <- if debugMode then IO.println(s"Simulation loop took ${endTime - startTime} ms") else IO.unit
121

122
              _ <- if stop then IO.unit else loop(nextState)
123
            yield ()
UNCOV
124

×
125
          loop(s)
126

×
127
        end simulationLoop
128

129
        private def nextStep(state: S, startTime: Long): IO[S] =
130
          state.simulationStatus match
×
UNCOV
131
            case SimulationStatus.RUNNING =>
×
132
              tickEvents(startTime, state.simulationSpeed.tickSpeed, state)
×
UNCOV
133

×
134
            case SimulationStatus.PAUSED =>
135
              IO.sleep(50.millis).as(state)
×
UNCOV
136

×
137
            case SimulationStatus.STOPPED =>
UNCOV
138
              IO.pure(state)
×
UNCOV
139

×
140
        private def runBehavior(queue: Queue[IO, Event], state: S): IO[Unit] =
141
          state.environment.entities.collect { case robot: Robot =>
×
UNCOV
142
            for
×
143
              sensorReadings <- robot.senseAll[IO](state.environment)
×
144
              maybeAction <- robot.behavior.run(sensorReadings)
×
145
              _ <- maybeAction match
146
                case Some(a) => queue.offer(Event.RobotAction(queue, robot, a))
147
                case None => IO.unit
148
            yield ()
149
          }.toList.parSequence.void
×
150

×
151
        private def tickEvents(start: Long, tickSpeed: FiniteDuration, state: S): IO[S] =
152
          for
×
153
            now <- Clock[IO].realTime.map(_.toMillis)
×
UNCOV
154
            timeToNextTick = tickSpeed.toMillis - (now - start)
×
155
            adjustedTickSpeed = if timeToNextTick > 0 then timeToNextTick else 0L
156
            sleepTime = FiniteDuration(adjustedTickSpeed, MILLISECONDS)
UNCOV
157
            _ <- IO.sleep(sleepTime)
×
158
            tick <- handleEvent(state, Event.Tick(tickSpeed))
159
          yield tick
UNCOV
160

×
161
        private def shuffleEvents(queue: Queue[IO, Event], state: S, events: Seq[Event]): IO[Seq[Event]] =
UNCOV
162
          val (controllerEvents, otherEvents) = events.partition:
×
UNCOV
163
            case _: Event.RobotAction => false
×
UNCOV
164
            case _ => true
×
UNCOV
165
          val (shuffledEvents, nextRNG: RNG) = state.simulationRNG generate (otherEvents shuffle)
×
UNCOV
166
          queue.offer(Event.Random(nextRNG)).as(controllerEvents ++ shuffledEvents)
×
UNCOV
167

×
168
        private def handleEvents(state: S, shuffledEvents: Seq[Event]): IO[S] =
UNCOV
169
          for finalState <- shuffledEvents.foldLeft(IO.pure(state)) { (taskState, event) =>
×
UNCOV
170
              for
×
171
                currentState <- taskState
172
                newState <- handleEvent(currentState, event)
173
              yield newState
174
            }
UNCOV
175
          yield finalState
×
UNCOV
176

×
177
        private def handleEvent(state: S, event: Event): IO[S] =
UNCOV
178
          event match
×
UNCOV
179
            case Event.Increment if state.simulationStatus == SimulationStatus.RUNNING =>
×
UNCOV
180
              context.model.increment(state)
×
UNCOV
181
            case Event.Tick(deltaTime) if state.simulationStatus == SimulationStatus.RUNNING =>
×
UNCOV
182
              context.model.tick(state, deltaTime)
×
UNCOV
183
            case Event.TickSpeed(speed) => context.model.tickSpeed(state, speed)
×
UNCOV
184
            case Event.Random(rng) => context.model.random(state, rng)
×
UNCOV
185
            case Event.Pause => context.model.pause(state)
×
UNCOV
186
            case Event.Resume => context.model.resume(state)
×
UNCOV
187
            case Event.Stop => context.model.stop(state)
×
UNCOV
188
            case Event.RobotAction(queue, robot, action) => context.model.handleRobotAction(state, queue, robot, action)
×
UNCOV
189
            case Event.CollisionDetected(queue, robot, updatedRobot) =>
×
UNCOV
190
              context.model.handleCollision(state, queue, robot, updatedRobot)
×
UNCOV
191
            case _ => IO.pure(state)
×
UNCOV
192

×
193
      end ControllerImpl
194

195
    end Controller
196

197
  end Component
198

199
  /**
200
   * Interface trait that combines the provider and component traits for the controller module.
201
   *
202
   * @tparam S
203
   *   the type of the simulation state, which must extend [[ModelModule.State]].
204
   */
205
  trait Interface[S <: ModelModule.State] extends Provider[S] with Component[S]:
206
    self: Requirements[S] =>
207
end ControllerModule
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc