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

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

21 Oct 2025 01:01PM UTC coverage: 76.131% (-2.2%) from 78.302%
#733

Pull #113

github

web-flow
Merge branch 'main' into feature/rl-controller
Pull Request #113: feat: create reinforcement learning controller

155 of 255 new or added lines in 26 files covered. (60.78%)

6 existing lines in 3 files now uncovered.

1515 of 1990 relevant lines covered (76.13%)

8.48 hits per line

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

88.89
/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

21
object RLControllerModule:
NEW
22

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

14✔
116
        var _state: S = initialState
117

12✔
118
        override def initialState: S = _initialState
119

3✔
120
        override def state: S = _state
121

3✔
122
        override def init(config: SimulationConfig[ValidEnvironment]): Unit =
123
          _initialState = bundle.stateLogic.createState(config)
124
          _state = _initialState
7✔
125

5✔
126
        override def reset(rng: RNG): (Observations, Infos) =
127
          _state = context.model.update(state)(using _ => bundle.stateLogic.updateState(initialState, rng))
128
          (state.environment.getObservations, state.environment.getInfos)
13✔
129

17✔
130
        override def step(actions: Map[Agent, Action[IO]]): StepResponse =
131
          _state = context.model.update(state)(using s => bundle.tickLogic.tick(s, state.dt)).unsafeRunSync()
132
          val actionsList =
16✔
133
            actions.map { (agent, action) =>
134
              DynamicEntityProposal(agent.copy(lastAction = Some(action)), action)
5✔
135
            }.toList.sortBy(_.entity.id)
136
          val prevState = state
8✔
137
          _state = context.model
3✔
138
            .update(state)(using
6✔
139
              s => bundle.dynamicEntityActionsLogic.handleDynamicEntityActionsProposals(s, actionsList),
2✔
140
            )
4✔
141
            .unsafeRunSync()
142
          _state = context.model
5✔
143
            .update(state)(using s => bundle.randomLogic.random(s, SimpleRNG(s.simulationRNG.nextLong._1)))
6✔
144
            .unsafeRunSync()
5✔
145
          StepResponse(
5✔
146
            observations = state.environment.getObservations,
1✔
147
            rewards = state.environment.getRewards(prevState.environment),
7✔
148
            terminateds = state.getTerminations(prevState),
9✔
149
            truncateds = state.getTruncations(prevState),
7✔
150
            infos = state.environment.getInfos,
7✔
151
          )
9✔
152

153
        end step
154

155
        override def render(width: Int, height: Int): Image =
156
          EnvironmentRenderer.renderToPNG(state.environment, width, height)
157
      end ControllerImpl
2✔
158
    end Controller
159

160
    extension (s: ModelModule.BaseState)
161

162
      def getTerminations(prev: ModelModule.BaseState): Terminateds =
163
        s.environment.entities.collect { case a: Agent =>
164
          a -> a.termination.evaluate(prev, s, a, a.lastAction.getOrElse(NoAction[IO]()))
22✔
165
        }.toMap
38✔
166

4✔
167
      def getTruncations(prev: ModelModule.BaseState): Truncateds =
168
        s.environment.entities.collect { case a: Agent =>
169
          a -> a.truncation.evaluate(prev, s, a, a.lastAction.getOrElse(NoAction[IO]()))
22✔
170
        }.toMap
38✔
171

4✔
172
    extension (env: ValidEnvironment)
173

174
      def getObservations: Observations =
175
        env.entities.collect { case a: Agent =>
176
          a -> a.senseAll[Id](env)
18✔
177
        }.toMap
29✔
178

4✔
179
      def getRewards(prev: ValidEnvironment): Rewards =
180
        env.entities.collect { case a: Agent =>
181
          a -> a.reward.evaluate(prev, env, a, a.lastAction.getOrElse(NoAction[IO]()))
21✔
182
        }.toMap
46✔
183

4✔
184
      def getInfos: Infos =
185
        env.entities.collect { case a: Agent =>
186
          a -> ""
15✔
187
        }.toMap
18✔
188
  end Component
4✔
189

190
  trait Interface[S <: ModelModule.BaseState] extends Provider[S] with Component[S]:
191
    self: Requirements[S] =>
192
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