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

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

27 Oct 2025 11:45AM UTC coverage: 73.259% (-3.3%) from 76.593%
#803

push

github

srs-mate
chore: format python

1578 of 2154 relevant lines covered (73.26%)

8.28 hits per line

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

91.3
/src/main/scala/io/github/srs/controller/RLControllerModule.scala
1
package io.github.srs.controller
2

3
import cats.effect.unsafe.implicits.global
4
import io.github.srs.model.ModelModule
5
import io.github.srs.config.SimulationConfig
6
import io.github.srs.model.environment.ValidEnvironment
7
import io.github.srs.utils.random.RNG
8
import io.github.srs.model.entity.dynamicentity.action.Action
9
import cats.Id
10
import io.github.srs.model.Simulation.simulation
11
import io.github.srs.model.logic.RLLogicsBundle
12
import io.github.srs.model.entity.dynamicentity.sensor.SensorReadings
13
import io.github.srs.model.entity.dynamicentity.sensor.Sensor.senseAll
14
import io.github.srs.view.rendering.EnvironmentRenderer
15
import io.github.srs.model.entity.dynamicentity.agent.Agent
16
import io.github.srs.controller.message.DynamicEntityProposal
17
import cats.effect.IO
18
import io.github.srs.utils.random.SimpleRNG
19
import io.github.srs.model.entity.dynamicentity.action.NoAction
20
import io.github.srs.logger
21

22
object RLControllerModule:
23

×
24
  type Observations = Map[Agent, SensorReadings]
25
  type Infos = Map[Agent, String]
5✔
26
  type Rewards = Map[Agent, Double]
27
  type Terminateds = Map[Agent, Boolean]
28
  type Truncateds = Map[Agent, Boolean]
29

30
  /**
31
   * The resonse after each simulation step.
32
   */
33
  case class StepResponse private[RLControllerModule] (
34
      observations: Observations,
35✔
35
      rewards: Rewards,
×
36
      terminateds: Terminateds,
×
37
      truncateds: Truncateds,
×
38
      infos: Infos,
×
39
  )
×
40

41
  /**
42
   * Controller trait defines the interface for a Reinforcement Learning controller.
43
   */
44
  trait Controller[S <: ModelModule.BaseState]:
45

46
    /**
47
     * The type of the image used for rendering the simulation on the RL client.
48
     */
49
    type Image = Array[Byte]
50

51
    /**
52
     * The initial state of the controller.
53
     */
54
    def initialState: S
55

56
    /**
57
     * The current state of the controller.
58
     */
59
    def state: S
60

61
    /**
62
     * Initializes the controller with the given simulation configuration.
63
     *
64
     * @param config
65
     *   the simulation configuration to initialize the controller.
66
     */
67
    def init(config: SimulationConfig[ValidEnvironment]): Unit
68

69
    /**
70
     * Resets the controller to its initial state using the provided random number generator.
71
     *
72
     * @param rng
73
     *   the random number generator to use for reproducibility in the next run.
74
     */
75
    def reset(rng: RNG): (Observations, Infos)
76

77
    /**
78
     * Performs a simulation step using the provided actions for each agent.
79
     *
80
     * @param actions
81
     *   a map of agents to their corresponding actions to be performed in this step.
82
     * @return
83
     *   a response containing the results of the simulation step.
84
     */
85
    def step(actions: Map[Agent, Action[IO]]): StepResponse
86

87
    /**
88
     * Renders the current state of the simulation to an image for the RL client.
89
     *
90
     * @param width
91
     *   the width of the rendered image.
92
     * @param height
93
     *   the height of the rendered image.
94
     *
95
     * @return
96
     *   an image representing the current state of the simulation.
97
     */
98
    def render(width: Int, height: Int): Image
99
  end Controller
100

101
  trait Provider[S <: ModelModule.BaseState]:
102
    val controller: Controller[S]
103

104
  type Requirements[S <: ModelModule.BaseState] = ModelModule.Provider[S]
105

106
  trait Component[S <: ModelModule.BaseState]:
107
    context: Requirements[S] =>
23✔
108

109
    object Controller:
110
      def apply()(using bundle: RLLogicsBundle[S]): Controller[S] = new ControllerImpl
5✔
111

9✔
112
      private class ControllerImpl(using bundle: RLLogicsBundle[S]) extends Controller[S]:
113

8✔
114
        var _initialState: S =
115
          bundle.stateLogic.createState(SimulationConfig(simulation, ValidEnvironment.empty))
4✔
116

14✔
117
        var _state: S = initialState
118

12✔
119
        override def initialState: S = _initialState
120

3✔
121
        override def state: S = _state
122

3✔
123
        override def init(config: SimulationConfig[ValidEnvironment]): Unit =
124
          _initialState = bundle.stateLogic.createState(config)
125
          _state = _initialState
7✔
126
          logger.debug(s"new config, state = \n $state")
4✔
127

8✔
128
        override def reset(rng: RNG): (Observations, Infos) =
129
          _state = context.model.update(state)(using _ => bundle.stateLogic.updateState(initialState, rng))
130
          logger.debug("resetting controller")
13✔
131
          (state.environment.getObservations, state.environment.getInfos)
7✔
132

17✔
133
        override def step(actions: Map[Agent, Action[IO]]): StepResponse =
134
          logger.debug("sending step command to controller")
135
          logger.debug(s"actions: $actions")
7✔
136
          _state = context.model.update(state)(using s => bundle.tickLogic.tick(s, state.dt)).unsafeRunSync()
7✔
137
          val actionsList =
16✔
138
            actions.map { (agent, action) =>
139
              DynamicEntityProposal(agent.copy(lastAction = Some(action), aliveSteps = agent.aliveSteps + 1), action)
5✔
140
            }.toList.sortBy(_.entity.id)
141
          val prevState = state
8✔
142
          _state = context.model
3✔
143
            .update(state)(using
6✔
144
              s => bundle.dynamicEntityActionsLogic.handleDynamicEntityActionsProposals(s, actionsList),
2✔
145
            )
4✔
146
            .unsafeRunSync()
147
          _state = context.model
5✔
148
            .update(state)(using s => bundle.randomLogic.random(s, SimpleRNG(s.simulationRNG.nextLong._1)))
6✔
149
            .unsafeRunSync()
5✔
150
          val observations = state.environment.getObservations
5✔
151
          val rewards = state.getRewards(prevState)
8✔
152
          val terminateds = state.getTerminations(prevState)
8✔
153
          val truncateds = state.getTruncations(prevState)
8✔
154
          val infos = state.environment.getInfos
8✔
155
          logger.debug(s"step completed, state = \n $state")
8✔
156
          logger.debug(s"observations: $observations")
7✔
157
          logger.debug(s"rewards: $rewards")
7✔
158
          logger.debug(s"terminateds: $terminateds")
7✔
159
          logger.debug(s"truncateds: $truncateds")
7✔
160
          logger.debug(s"infos: $infos")
7✔
161
          StepResponse(
7✔
162
            observations,
1✔
163
            rewards,
1✔
164
            terminateds,
1✔
165
            truncateds,
1✔
166
            infos,
1✔
167
          )
3✔
168

169
        end step
170

171
        override def render(width: Int, height: Int): Image =
172
          EnvironmentRenderer.renderToPNG(state.environment, width, height)
173
      end ControllerImpl
2✔
174
    end Controller
175

176
    extension (s: ModelModule.BaseState)
177

178
      def getTerminations(prev: ModelModule.BaseState): Terminateds =
179
        s.environment.entities.collect { case a: Agent =>
180
          a -> a.termination.evaluate(prev, s, a, a.lastAction.getOrElse(NoAction[IO]()))
22✔
181
        }.toMap
38✔
182

4✔
183
      def getTruncations(prev: ModelModule.BaseState): Truncateds =
184
        s.environment.entities.collect { case a: Agent =>
185
          a -> a.truncation.evaluate(prev, s, a, a.lastAction.getOrElse(NoAction[IO]()))
22✔
186
        }.toMap
38✔
187

4✔
188
      def getRewards(prev: ModelModule.BaseState): Rewards =
189
        s.environment.entities.collect { case a: Agent =>
190
          a -> a.reward.evaluate(prev, s, a, a.lastAction.getOrElse(NoAction[IO]()))
22✔
191
        }.toMap
38✔
192

4✔
193
    extension (env: ValidEnvironment)
194

195
      def getObservations: Observations =
196
        env.entities.collect { case a: Agent =>
197
          a -> a.senseAll[Id](env)
18✔
198
        }.toMap
29✔
199

4✔
200
      def getInfos: Infos =
201
        env.entities.collect { case a: Agent =>
202
          a -> ""
15✔
203
        }.toMap
18✔
204
  end Component
4✔
205

206
  trait Interface[S <: ModelModule.BaseState] extends Provider[S] with Component[S]:
207
    self: Requirements[S] =>
208
end RLControllerModule
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