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

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

07 Aug 2025 04:34PM UTC coverage: 71.429% (-12.6%) from 84.0%
#208

Pull #22

github

GiuliaNardicchia
refactor: fix suggestions
Pull Request #22: feat: simulation engine

61 of 229 new or added lines in 16 files covered. (26.64%)

3 existing lines in 1 file now uncovered.

650 of 910 relevant lines covered (71.43%)

10.52 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.compiletime.deferred
4
import scala.concurrent.duration.FiniteDuration
5

6
import cats.syntax.foldable.toFoldableOps
7
import io.github.srs.model.*
8
import io.github.srs.model.SimulationConfig.SimulationStatus
9
import io.github.srs.model.UpdateLogic.*
10
import io.github.srs.model.logic.*
11
import monix.catnap.ConcurrentQueue
12
import monix.eval.Task
13

14
/**
15
 * Module that defines the controller logic for the Scala Robotics Simulator.
16
 */
17
object ControllerModule:
18

×
19
  /**
20
   * Controller trait that defines the interface for the controller.
21
   *
22
   * @tparam S
23
   *   the type of the state, which must extend [[ModelModule.State]].
24
   */
25
  trait Controller[S <: ModelModule.State]:
26
    /**
×
27
     * Starts the controller with the initial state.
28
     *
29
     * @param initialState
30
     *   the initial state of the simulation.
31
     */
32
    def start(initialState: S): Task[Unit]
33

34
    /**
35
     * Runs the simulation loop, processing events from the queue and updating the state.
36
     *
37
     * @param s
38
     *   the current state of the simulation.
39
     * @param queue
40
     *   a concurrent queue that holds events to be processed.
41
     * @return
42
     *   a task that completes when the simulation loop ends.
43
     */
44
    def simulationLoop(s: S, queue: ConcurrentQueue[Task, Event]): Task[Unit]
45

46
  end Controller
47

48
  /**
49
   * Provider trait that defines the interface for providing a controller.
50
   * @tparam S
51
   *   the type of the state, which must extend [[ModelModule.State]].
52
   */
53
  trait Provider[S <: ModelModule.State]:
54
    val controller: Controller[S]
55

56
  /**
57
   * Defines the dependencies required by the controller module. In particular, it requires a
58
   * [[io.github.srs.view.ViewModule.Provider]] and a [[io.github.srs.model.ModelModule.Provider]].
59
   */
60
  type Requirements[S <: ModelModule.State] =
61
    io.github.srs.view.ViewModule.Provider[S] & io.github.srs.model.ModelModule.Provider[S]
62

63
  /**
64
   * Component trait that defines the interface for creating a controller.
65
   * @tparam S
66
   *   the type of the simulation state, which must extend [[ModelModule.State]].
67
   */
68
  trait Component[S <: ModelModule.State]:
69
    context: Requirements[S] =>
×
70
    given inc: IncrementLogic[S] = deferred
71
    given tick: TickLogic[S] = deferred
72
    given pause: PauseLogic[S] = deferred
73
    given resume: ResumeLogic[S] = deferred
74
    given stop: StopLogic[S] = deferred
75

76
    object Controller:
77
      /**
×
78
       * Creates a controller instance.
79
       *
80
       * @return
81
       *   a [[Controller]] instance.
82
       */
83
      def apply(): Controller[S] = new ControllerImpl
84

×
85
      /**
86
       * Private controller implementation that delegates the simulation loop to the provided model and view.
87
       */
88
      private class ControllerImpl extends Controller[S]:
89

×
90
        override def start(initialState: S): Task[Unit] =
NEW
91
          val MAX_COUNT = 10_000
×
NEW
92
          val randInt: Int = initialState.simulationRNG.nextIntBetween(0, MAX_COUNT)._1
×
NEW
93
          val list = List.fill(randInt)(Event.Increment)
×
NEW
94
          for
×
NEW
95
            queueSim <- ConcurrentQueue.unbounded[Task, Event]()
×
NEW
96
            _ <- context.view.init(queueSim)
×
97
            //            queueLog <- ConcurrentQueue.unbounded[Task, Event]()
98
            _ <- produceEvents(queueSim, list)
99
            _ <- simulationLoop(initialState, queueSim)
100
          //            _ <- Task.parMap2(
101
          //              simulationLoop(initialState, queueSim),
102
          //              consumeStream(queueLog)(event => Task(println(s"Received: $event")))
103
          //            )((_, _) => ())
104
          yield ()
NEW
105

×
106
        override def simulationLoop(s: S, queue: ConcurrentQueue[Task, Event]): Task[Unit] =
NEW
107
          def loop(state: S): Task[Unit] =
×
NEW
108
            for
×
NEW
109
              events <- queue.drain(0, 50)
×
NEW
110
              newState <- handleEvents(events, state)
×
111
              _ <- context.view.render(newState)
112
              nextState <-
113
                if newState.simulationStatus == SimulationStatus.RUNNING then
114
                  tickEvents(newState.simulationSpeed.tickSpeed, newState)
115
                else Task.pure(newState)
116
              stop = newState.simulationStatus == SimulationStatus.STOPPED ||
117
                newState.simulationTime.exists(max => newState.elapsedTime >= max)
118
              _ <- if stop then Task.unit else loop(nextState)
119
            yield ()
NEW
120

×
121
          loop(s)
NEW
122

×
123
        //        private def consumeStream[A](queue: ConcurrentQueue[Task, A])(consume: A => Task[Unit]): Task[Unit] =
124
        //          Observable
125
        //            .repeatEvalF(queue.poll)
126
        //            .mapEval(consume)
127
        //            .completedL
128

129
        private def produceEvents[A](queue: ConcurrentQueue[Task, A], events: List[A]): Task[Unit] =
NEW
130
          events.traverse_(queue.offer)
×
NEW
131

×
132
        private def tickEvents(tickSpeed: FiniteDuration, state: S): Task[S] =
NEW
133
          for
×
NEW
134
            _ <- Task.sleep(tickSpeed)
×
NEW
135
            tick <- handleEvent(Event.Tick(tickSpeed), state)
×
136
          yield tick
NEW
137

×
138
        private def handleEvents(events: Seq[Event], state: S): Task[S] =
NEW
139
          for finalState <- events.foldLeft(Task.pure(state)) { (taskState, event) =>
×
NEW
140
              for
×
141
                currentState <- taskState
142
                newState <- handleEvent(event, currentState)
143
              yield newState
144
            }
NEW
145
          yield finalState
×
NEW
146

×
147
        private def handleEvent(event: Event, state: S): Task[S] =
NEW
148
          event match
×
NEW
149
            case Event.Increment if state.simulationStatus == SimulationStatus.RUNNING =>
×
NEW
150
              context.model.increment(state)
×
NEW
151
            case Event.Tick(deltaTime) if state.simulationStatus == SimulationStatus.RUNNING =>
×
NEW
152
              context.model.tick(state, deltaTime)
×
NEW
153
            case Event.Pause => context.model.pause(state)
×
NEW
154
            case Event.Resume => context.model.resume(state)
×
NEW
155
            case Event.Stop => context.model.stop(state)
×
NEW
156
            case Event.TickSpeed(speed) => context.model.tickSpeed(state, speed)
×
NEW
157
            case _ => Task.pure(state)
×
NEW
158

×
159
      end ControllerImpl
160

161
    end Controller
162

163
  end Component
164

165
  /**
166
   * Interface trait that combines the provider and component traits for the controller module.
167
   *
168
   * @tparam S
169
   *   the type of the simulation state, which must extend [[ModelModule.State]].
170
   */
171
  trait Interface[S <: ModelModule.State] extends Provider[S] with Component[S]:
172
    self: Requirements[S] =>
173
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