Skip to content

Commit 78713f7

Browse files
CopilotbedaHovorka
andcommitted
refactor: Implement SimulationProcessFactory pattern
Phase 2 complete - Factory abstraction created: - Add SimulationProcessFactory interface (context package) - Add DefaultSimulationProcessFactory implementation (sim package) - Update DefaultContext to use factory instead of direct instantiation - Remove direct imports of Generator and InOutWorker from DefaultContext - Update XMLContextFactory to inject factory from Koin - Configure Koin DI to provide factory singleton - Change setMainProcess to accept LoopProcess (more general) Benefits: - DefaultContext no longer directly depends on concrete simulation classes - Follows Dependency Inversion Principle - Easier to test contexts with mock factories - Prepared for future jDisco→DSOL/Kalasim migration Co-authored-by: bedaHovorka <5263405+bedaHovorka@users.noreply.github.com>
1 parent c0b3d84 commit 78713f7

5 files changed

Lines changed: 191 additions & 9 deletions

File tree

src/main/kotlin/cz/vutbr/fit/interlockSim/context/DefaultContext.kt

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,8 @@ import cz.vutbr.fit.interlockSim.objects.tracks.SimpleTrackBlock
2323
import cz.vutbr.fit.interlockSim.objects.tracks.Track
2424
import cz.vutbr.fit.interlockSim.objects.tracks.TrackBlock
2525
import cz.vutbr.fit.interlockSim.objects.tracks.TrackSection
26-
import cz.vutbr.fit.interlockSim.sim.Generator
2726
import cz.vutbr.fit.interlockSim.sim.InOutWorker
2827
import cz.vutbr.fit.interlockSim.sim.LoopProcess
29-
import cz.vutbr.fit.interlockSim.sim.ShuntingLoop
3028
import cz.vutbr.fit.interlockSim.exceptions.SimulationException
3129
import cz.vutbr.fit.interlockSim.exceptions.requireSimulation
3230
import cz.vutbr.fit.interlockSim.exceptions.requireSimulationNotNull
@@ -89,7 +87,13 @@ import java.util.TreeMap
8987
* @see SimulationContext
9088
* @see javax.annotation.concurrent.NotThreadSafe
9189
*/
92-
abstract class DefaultContext :
90+
abstract class DefaultContext(
91+
/**
92+
* Factory for creating simulation processes.
93+
* Decouples context from concrete simulation class implementations.
94+
*/
95+
private val processFactory: SimulationProcessFactory
96+
) :
9397
EditingContext,
9498
SimulationContext {
9599
/**
@@ -173,7 +177,11 @@ abstract class DefaultContext :
173177
const val SQRT2: Double = 1.4142135623730951
174178
}
175179

176-
protected constructor(cols: Int, rows: Int) {
180+
protected constructor(
181+
cols: Int,
182+
rows: Int,
183+
processFactory: SimulationProcessFactory
184+
) : this(processFactory) {
177185
this.railwayNetGrid = DefaultRailWayNetGrid(cols, rows)
178186
logger.debug { "Initialized railway network grid: ${cols}x$rows cells" }
179187
}
@@ -784,15 +792,19 @@ abstract class DefaultContext :
784792
}
785793
throw EmptyContextException()
786794
}
787-
if (mainProcess == null) mainProcess = Generator(this)
795+
// Use factory to create main process if not already set
796+
if (mainProcess == null) {
797+
mainProcess = processFactory.createMainProcess(this)
798+
}
788799

789800
logger.info {
790801
"Starting simulation: ${inouts.size} InOut points, ${getGraph().size()} track blocks, " +
791802
"main process=${mainProcess!!.javaClass.simpleName}"
792803
}
793804

805+
// Use factory to create worker for each InOut
794806
for (i in inouts) {
795-
workers[i] = InOutWorker(this, i)
807+
workers[i] = processFactory.createInOutWorker(this, i)
796808
}
797809

798810
try {
@@ -802,6 +814,10 @@ abstract class DefaultContext :
802814
throw SimulationException(e)
803815
}
804816
}
817+
logger.error(e) { "Failed to activate main simulation process" }
818+
throw SimulationException(e)
819+
}
820+
}
805821

806822
/**
807823
* Stop the simulation
@@ -959,8 +975,10 @@ abstract class DefaultContext :
959975
/**
960976
* Set the main process for the simulation
961977
* (for examples where the main process is not a generator)
978+
*
979+
* @param process The custom main process (e.g., ShuntingLoop)
962980
*/
963-
fun setMainProcess(loop: ShuntingLoop) {
964-
mainProcess = loop
981+
fun setMainProcess(process: LoopProcess) {
982+
mainProcess = process
965983
}
966984
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/* Brno University of Technology
2+
* Faculty of Information Technology
3+
*
4+
* BSc Thesis 2006/2007
5+
*
6+
* Railway Interlocking Simulator
7+
*
8+
* Bedrich Hovorka
9+
*/
10+
package cz.vutbr.fit.interlockSim.context
11+
12+
import cz.vutbr.fit.interlockSim.objects.cells.InOut
13+
import cz.vutbr.fit.interlockSim.sim.InOutWorker
14+
import cz.vutbr.fit.interlockSim.sim.LoopProcess
15+
16+
/**
17+
* Factory interface for creating simulation processes.
18+
*
19+
* Decouples context from concrete simulation class implementations by using
20+
* the Factory pattern. This allows:
21+
* - Different simulation engines to be plugged in
22+
* - Testing without concrete simulation dependencies
23+
* - Future migration from jDisco to DSOL/Kalasim
24+
*
25+
* ## Design Decision
26+
*
27+
* This interface is placed in the context package (not sim/) because:
28+
* - It's an abstraction used by contexts
29+
* - Only the implementation needs knowledge of concrete sim/ classes
30+
* - Follows Dependency Inversion Principle
31+
*
32+
* ## Future Migration
33+
*
34+
* When migrating from jDisco to DSOL/Kalasim:
35+
* 1. Create new factory implementation with DSOL/Kalasim classes
36+
* 2. Update DI configuration to use new factory
37+
* 3. All context code continues to work unchanged
38+
*
39+
* @see DefaultSimulationContext
40+
* @see SimulationContext
41+
*/
42+
interface SimulationProcessFactory {
43+
/**
44+
* Create the main simulation process.
45+
*
46+
* The main process is responsible for generating trains and managing
47+
* the overall simulation flow. Default implementation creates a Generator.
48+
*
49+
* @param context The simulation context
50+
* @return Main process for the simulation (e.g., Generator)
51+
*/
52+
fun createMainProcess(context: SimulationContext): LoopProcess
53+
54+
/**
55+
* Create worker process for an InOut point.
56+
*
57+
* Each InOut (entry/exit point) needs a worker process to handle
58+
* trains entering and leaving the railway network.
59+
*
60+
* @param context The simulation context
61+
* @param inOut The InOut point to create worker for
62+
* @return Worker process for the InOut
63+
*/
64+
fun createInOutWorker(
65+
context: SimulationContext,
66+
inOut: InOut
67+
): InOutWorker
68+
}

src/main/kotlin/cz/vutbr/fit/interlockSim/di/InterlockSimModule.kt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ import cz.vutbr.fit.interlockSim.MyResourceBundle
1414
import cz.vutbr.fit.interlockSim.ExampleRegistry
1515
import cz.vutbr.fit.interlockSim.context.EditingContextFactory
1616
import cz.vutbr.fit.interlockSim.context.SimulationContextFactory
17+
import cz.vutbr.fit.interlockSim.context.SimulationProcessFactory
1718
import cz.vutbr.fit.interlockSim.gui.Frame
19+
import cz.vutbr.fit.interlockSim.sim.DefaultSimulationProcessFactory
1820
import cz.vutbr.fit.interlockSim.xml.XMLContextFactory
1921
import org.koin.core.module.Module
2022
import org.koin.dsl.module
@@ -76,11 +78,20 @@ val editingModule: Module = module {
7678
/**
7779
* Simulation module
7880
*
81+
* Provides simulation-related dependencies:
82+
* - SimulationProcessFactory for creating simulation processes
83+
* - SimulationContextFactory for creating simulation contexts
84+
* - ExampleRegistry for managing simulation examples
7985
*
8086
* @see SimulationContextFactory
87+
* @see SimulationProcessFactory
8188
* @see XMLContextFactory
8289
*/
8390
val simulationModule: Module = module {
91+
// Factory for creating simulation processes (Generator, InOutWorker)
92+
// Singleton as factory is stateless
93+
single<SimulationProcessFactory> { DefaultSimulationProcessFactory() }
94+
8495
// XMLContextFactory is now defined in xmlModule as a singleton, but not in future
8596
// Bind factory interfaces to the singleton XMLContextFactory instance
8697
single<SimulationContextFactory> { get<XMLContextFactory>() }
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/* Brno University of Technology
2+
* Faculty of Information Technology
3+
*
4+
* BSc Thesis 2006/2007
5+
*
6+
* Railway Interlocking Simulator
7+
*
8+
* Bedrich Hovorka
9+
*/
10+
package cz.vutbr.fit.interlockSim.sim
11+
12+
import cz.vutbr.fit.interlockSim.context.SimulationContext
13+
import cz.vutbr.fit.interlockSim.context.SimulationProcessFactory
14+
import cz.vutbr.fit.interlockSim.objects.cells.InOut
15+
import io.github.oshai.kotlinlogging.KotlinLogging
16+
17+
/**
18+
* Default implementation of SimulationProcessFactory using jDisco-based classes.
19+
*
20+
* This factory creates concrete simulation process instances:
21+
* - Generator: Main process for generating trains
22+
* - InOutWorker: Worker processes for InOut points
23+
*
24+
* ## Implementation Note
25+
*
26+
* This class is intentionally placed in the sim/ package because:
27+
* - It has direct dependencies on concrete simulation classes
28+
* - It's the only place that needs to know about Generator and InOutWorker
29+
* - Keeps simulation implementation details in sim/ package
30+
*
31+
* ## Future Migration
32+
*
33+
* When migrating from jDisco to DSOL/Kalasim:
34+
* 1. Create DSOLSimulationProcessFactory (or similar)
35+
* 2. Update Koin DI configuration to use new factory
36+
* 3. All context code continues to work unchanged
37+
* 4. This class can be deprecated or kept for backwards compatibility
38+
*
39+
* @see SimulationProcessFactory
40+
* @see Generator
41+
* @see InOutWorker
42+
*/
43+
class DefaultSimulationProcessFactory : SimulationProcessFactory {
44+
companion object {
45+
private val logger = KotlinLogging.logger {}
46+
}
47+
48+
/**
49+
* Create main simulation process (Generator).
50+
*
51+
* Generator is responsible for:
52+
* - Creating trains according to schedules
53+
* - Managing simulation timing
54+
* - Coordinating overall simulation flow
55+
*
56+
* @param context The simulation context
57+
* @return Generator process instance
58+
*/
59+
override fun createMainProcess(context: SimulationContext): LoopProcess {
60+
logger.debug { "Creating main Generator process for simulation" }
61+
return Generator(context)
62+
}
63+
64+
/**
65+
* Create worker process for an InOut point.
66+
*
67+
* InOutWorker is responsible for:
68+
* - Managing trains entering/exiting at this InOut
69+
* - Waiting for path availability
70+
* - Coordinating with interlocking system
71+
*
72+
* @param context The simulation context
73+
* @param inOut The InOut point to create worker for
74+
* @return InOutWorker process instance
75+
*/
76+
override fun createInOutWorker(
77+
context: SimulationContext,
78+
inOut: InOut
79+
): InOutWorker {
80+
logger.debug { "Creating InOutWorker for InOut: ${inOut.getName()}" }
81+
return InOutWorker(context, inOut)
82+
}
83+
}

src/main/kotlin/cz/vutbr/fit/interlockSim/xml/XMLContextFactory.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import cz.vutbr.fit.interlockSim.context.DefaultContext
1616
import cz.vutbr.fit.interlockSim.context.EditingContext
1717
import cz.vutbr.fit.interlockSim.context.EditingContextFactory
1818
import cz.vutbr.fit.interlockSim.context.SimulationContextFactory
19+
import cz.vutbr.fit.interlockSim.context.SimulationProcessFactory
1920
import cz.vutbr.fit.interlockSim.objects.cells.Cell
2021
import cz.vutbr.fit.interlockSim.objects.cells.Cell.Segment
2122
import cz.vutbr.fit.interlockSim.objects.cells.Cell.SpatialType
@@ -64,13 +65,14 @@ class XMLContextFactory :
6465
SimulationContextFactory {
6566

6667
private val myResourceBundle: MyResourceBundle by getKoin().inject()
68+
private val processFactory: SimulationProcessFactory by getKoin().inject()
6769

6870
// TODO: Validate track length >= train length - see issue #60 (relates to Goals 3 & 4)
6971

7072
private inner class XMLContext(
7173
cols: Int,
7274
rows: Int
73-
) : DefaultContext(cols, rows) {
75+
) : DefaultContext(cols, rows, processFactory) {
7476
// No additional implementation needed
7577
}
7678

0 commit comments

Comments
 (0)