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

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

21 Oct 2025 08:53AM UTC coverage: 76.161% (-2.1%) from 78.302%
#727

Pull #113

github

sceredi
feat: add observations and infos to step along with stepping logic
Pull Request #113: feat: create reinforcement learning controller

134 of 226 new or added lines in 21 files covered. (59.29%)

6 existing lines in 3 files now uncovered.

1492 of 1959 relevant lines covered (76.16%)

8.38 hits per line

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

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

20
object RLControllerModule:
NEW
21

×
22
  type Observations = Map[Agent, SensorReadings]
23
  type Infos = Map[Agent, String]
5✔
24

25
  /**
26
   * The resonse after each simulation step.
27
   */
28
  case class StepResponse private[RLControllerModule] (
29
      observations: Observations,
35✔
NEW
30
      rewards: Map[Agent, Double],
×
NEW
31
      terminateds: Map[Agent, Boolean],
×
NEW
32
      truncateds: Map[Agent, Boolean],
×
NEW
33
      infos: Infos,
×
NEW
34
  )
×
35

36
  /**
37
   * Controller trait defines the interface for a Reinforcement Learning controller.
38
   */
39
  trait Controller[S <: ModelModule.BaseState]:
40

41
    /**
42
     * The type of the image used for rendering the simulation on the RL client.
43
     */
44
    type Image = Array[Byte]
45

46
    /**
47
     * The initial state of the controller.
48
     */
49
    def initialState: S
50

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

56
    /**
57
     * Initializes the controller with the given simulation configuration.
58
     *
59
     * @param config
60
     *   the simulation configuration to initialize the controller.
61
     */
62
    def init(config: SimulationConfig[ValidEnvironment]): Unit
63

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

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

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

96
  trait Provider[S <: ModelModule.BaseState]:
97
    val controller: Controller[S]
98

99
  type Requirements[S <: ModelModule.BaseState] = ModelModule.Provider[S]
100

101
  trait Component[S <: ModelModule.BaseState]:
102
    context: Requirements[S] =>
8✔
103

104
    object Controller:
105
      def apply()(using bundle: RLLogicsBundle[S]): Controller[S] = new ControllerImpl
5✔
106

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

8✔
109
        var _initialState: S =
110
          bundle.stateLogic.createState(SimulationConfig(simulation, ValidEnvironment.empty))
4✔
111

14✔
112
        var _state: S = initialState
113

12✔
114
        override def initialState: S = _initialState
115

3✔
116
        override def state: S = _state
117

3✔
118
        override def init(config: SimulationConfig[ValidEnvironment]): Unit =
119
          _initialState = bundle.stateLogic.createState(config)
120
          _state = _initialState
7✔
121

5✔
122
        override def reset(rng: RNG): (Observations, Infos) =
123
          _state = context.model.update(state)(using _ => bundle.stateLogic.updateState(initialState, rng))
124
          (state.environment.createObservations, state.environment.createInfos)
13✔
125

17✔
126
        override def step(actions: Map[Agent, Action[IO]]): StepResponse =
127
          _state = context.model.update(state)(using s => bundle.tickLogic.tick(s, state.dt)).unsafeRunSync()
128
          val actionsList =
16✔
129
            actions.map { (agent, action) => DynamicEntityProposal(agent, action) }.toList.sortBy(_.entity.id)
130
          _state = context.model
13✔
131
            .update(state)(using
6✔
132
              s => bundle.dynamicEntityActionsLogic.handleDynamicEntityActionsProposals(s, actionsList),
2✔
133
            )
4✔
134
            .unsafeRunSync()
135
          _state = context.model
5✔
136
            .update(state)(using s => bundle.randomLogic.random(s, SimpleRNG(s.simulationRNG.nextLong._1)))
6✔
137
            .unsafeRunSync()
5✔
138
          StepResponse(
5✔
139
            observations = state.environment.createObservations,
1✔
140
            rewards = Map.empty,
7✔
141
            terminateds = Map.empty,
3✔
142
            truncateds = Map.empty,
3✔
143
            infos = state.environment.createInfos,
3✔
144
          )
9✔
145

146
        override def render(width: Int, height: Int): Image =
147
          EnvironmentRenderer.renderToPNG(state.environment, width, height)
148
      end ControllerImpl
2✔
149
    end Controller
150

151
    extension (env: ValidEnvironment)
152

153
      def createObservations: Observations =
154
        env.entities.collect { case a: Agent =>
155
          a -> a.senseAll[Id](env)
18✔
156
        }.toMap
29✔
157

4✔
158
      def createInfos: Infos =
159
        env.entities.collect { case a: Agent =>
160
          a -> ""
15✔
161
        }.toMap
18✔
162
  end Component
4✔
163

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