Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 44 additions & 9 deletions plugins/points/points.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ package points

import (
"github.com/ts2/ts2-sim-server/simulation"
"math/rand"
"sync"
"time"
)

// StandardManager is a points manager that performs points change
Expand All @@ -31,24 +33,57 @@ type StandardManager struct {
}

// Direction returns the direction of the points
func (sm StandardManager) Direction(p *simulation.PointsItem) simulation.PointDirection {
func (sm *StandardManager) Direction(p *simulation.PointsItem) simulation.PointDirection {
sm.RLock()
defer sm.RUnlock()
return sm.directions[p.ID()]
}

// SetDirection tries to set the given PointsItem to the given direction
// SetDirection tries to set the given PointsItem to the given direction.
//
// You should not assume that the direction has been set, since this can be
// delayed or failed. Call Direction to check.
func (sm *StandardManager) SetDirection(p *simulation.PointsItem, dir simulation.PointDirection) {
// Just after the function is called, the points will switch to DirectionUnknown.
// When the points are in position, the notify channel is closed.
func (sm *StandardManager) SetDirection(p *simulation.PointsItem, dir simulation.PointDirection, notify chan struct{}) {
if dir == simulation.DirectionCurrent {
return
}
startTime := p.Simulation().CurrentTime()
delay := time.Duration(3+rand.Intn(3)) * time.Second
if sm.directions[p.ID()] == dir {
// Points are in the correct direction already
if p.PairedItem() != nil {
if sm.directions[p.PairedTiId] != dir {
sm.SetDirection(p.PairedItem(), dir, notify)
} else {
close(notify)
}
}
return
}
// Check notify != nil to prevent infinite recursion
if p.PairedItem() != nil && notify != nil {
sm.SetDirection(p.PairedItem(), dir, nil)
}
go func() {
for {
<-time.After(simulation.TimeStep)
if p.Simulation().CurrentTime().Sub(startTime.Add(delay)) > 0 {
break
}
}
sm.Lock()
defer sm.Unlock()
sm.directions[p.ID()] = dir
if p.PairedItem() != nil {
sm.directions[p.PairedItem().ID()] = dir
}
if notify != nil {
close(notify)
}
}()
sm.Lock()
defer sm.Unlock()
sm.directions[p.ID()] = dir
if p.PairedItem() != nil {
sm.directions[p.PairedItem().ID()] = dir
}
sm.directions[p.ID()] = simulation.DirectionUnknown
}

// Name returns a description of this manager that is used for the UI.
Expand Down
3 changes: 2 additions & 1 deletion server/hub_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -673,7 +673,8 @@ func TestHub(t *testing.T) {
So(resp.Data.Status, ShouldEqual, Ok)

resp = sendRequestStatus(c, "route", "activate", `{"id": "2"}`)
So(resp.Data.Status, ShouldEqual, Ok)
// Fails because route 1 did not have time to deactivate
So(resp.Data.Status, ShouldEqual, Fail)

err = c.WriteJSON(RequestListener{
Object: "server",
Expand Down
2 changes: 1 addition & 1 deletion simulation/loading_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ func TestSimulationLoading(t *testing.T) {
So(ok, ShouldBeTrue)
So(d1, ShouldEqual, DirectionNormal)
So(r1.InitialState, ShouldEqual, Activated)
So(r1.State(), ShouldEqual, Activated)
So(r1.State(), ShouldEqual, Activating)

So(sim.Routes, ShouldContainKey, "4")
r4, ok := sim.Routes["4"]
Expand Down
3 changes: 3 additions & 0 deletions simulation/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ type Options struct {
WrongPlatformPenalty int `json:"wrongPlatformPenalty"`
WrongDestinationPenalty int `json:"wrongDestinationPenalty"`
LatePenalty int `json:"latePenalty"`
PointsSetupDelay DelayGenerator `json:"pointsSetupDelay"`
RoutesSetupDelay DelayGenerator `json:"routesSetupDelay"`
RoutesCancelDelay DelayGenerator `json:"routesCancelDelay"`

simulation *Simulation
}
Expand Down
72 changes: 69 additions & 3 deletions simulation/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ package simulation
import (
"encoding/json"
"fmt"
"time"
)

// A RoutesManager checks if a route is activable or deactivable.
Expand Down Expand Up @@ -49,6 +50,9 @@ const (

// Destroying = The route is currently being destroyed by a train
Destroying RouteState = 3

// Activating - The route is currently being actived
Activating RouteState = 4
)

// A Route is a path between two signals.
Expand All @@ -67,6 +71,7 @@ type Route struct {
Positions []Position `json:"-"`

simulation *Simulation
activating bool
triggers []func(*Route)
}

Expand All @@ -93,6 +98,9 @@ func (r *Route) Equals(other *Route) bool {

// State returns the current state of this route
func (r *Route) State() RouteState {
if r.activating {
return Activating
}
if r.BeginSignal().nextActiveRoute == nil || !r.BeginSignal().nextActiveRoute.Equals(r) {
for _, p := range r.Positions {
if p.TrackItem().ActiveRoute() != nil && p.TrackItem().ActiveRoute().Equals(r) {
Expand All @@ -112,6 +120,11 @@ func (r *Route) IsActive() bool {
return r.State() == Activated || r.State() == Persistent
}

// IsDestroying returns true if this Route is currently being destroyed
func (r *Route) IsDestroying() bool {
return r.State() == Destroying
}

// addTrigger adds the given function to the list of function that will be
// called when this Route is activated or deactivated.
func (r *Route) addTrigger(trigger func(*Route)) {
Expand All @@ -125,15 +138,46 @@ func (r *Route) Activate(persistent bool) error {
return fmt.Errorf("%s vetoed route activation: %s", rm.Name(), err)
}
}
r.activating = true
for _, pos := range r.Positions {
if pos.TrackItem().Equals(r.BeginSignal()) || pos.TrackItem().Equals(r.EndSignal()) {
continue
}
pos.TrackItem().setActiveRoute(r, pos.PreviousItem())
}
r.Persistent = persistent
r.simulation.sendEvent(&Event{
Name: RouteActivatedEvent,
Object: r,
})
go func() {
routesDelay := r.simulation.Options.RoutesSetupDelay.Yield()
startTime := r.simulation.CurrentTime()
waitLoop:
for {
<-time.After(TimeStep)
for _, pos := range r.Positions {
if points, ok := pos.TrackItem().(*PointsItem); ok {
if points.Moving() {
continue waitLoop
}
}
}
if r.simulation.CurrentTime().Sub(startTime.Add(routesDelay)) < 0 {
continue
}
break
}
r.simulation.activatedRoutesChan <- r
}()
return nil
}

// doActivate sets the route to activated state at the end of the activation process.
func (r *Route) doActivate() {
r.activating = false
r.EndSignal().previousActiveRoute = r
r.BeginSignal().nextActiveRoute = r
r.Persistent = persistent
for _, t := range r.triggers {
t(r)
}
Expand All @@ -142,7 +186,6 @@ func (r *Route) Activate(persistent bool) error {
Object: r,
})
r.BeginSignal().updateSignalState()
return nil
}

// Deactivate the given route. If the route cannot be Deactivated, an error is returned.
Expand All @@ -152,6 +195,30 @@ func (r *Route) Deactivate() error {
return fmt.Errorf("%s vetoed route deactivation", rm.Name())
}
}
r.activating = true
r.simulation.sendEvent(&Event{
Name: RouteDeactivatedEvent,
Object: r,
})
r.BeginSignal().updateSignalState()
go func() {
routesDelay := r.simulation.Options.RoutesCancelDelay.Yield()
startTime := r.simulation.CurrentTime()
for {
<-time.After(TimeStep)
if r.simulation.CurrentTime().Sub(startTime.Add(routesDelay)) > 0 {
break
}
}
r.simulation.deactivatedRoutesChan <- r
}()
return nil
}

// doDeactivate sets the route's status to Deactivated at the end of the deactivation process.
func (r *Route) doDeactivate() {
r.activating = false
r.Persistent = false
r.BeginSignal().resetNextActiveRoute(r)
r.EndSignal().resetPreviousActiveRoute(nil)
for _, pos := range r.Positions {
Expand All @@ -168,7 +235,6 @@ func (r *Route) Deactivate() error {
Object: r,
})
r.BeginSignal().updateSignalState()
return nil
}

// setSimulation sets the Simulation this Route is part of.
Expand Down
4 changes: 2 additions & 2 deletions simulation/signal_conditions.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ func (nar NextActiveRoute) Code() string {

// Solve returns if the condition is met for the given SignalItem and parameters
func (nar NextActiveRoute) Solve(item *SignalItem, values []string, params []string) bool {
return item.nextActiveRoute != nil
return item.nextActiveRoute != nil && item.nextActiveRoute.IsActive()
}

// SetupTriggers installs needed triggers for the given SignalItem, with the
Expand All @@ -52,7 +52,7 @@ func (par PreviousActiveRoute) Code() string {

// Solve returns if the condition is met for the given SignalItem and parameters
func (par PreviousActiveRoute) Solve(item *SignalItem, values []string, params []string) bool {
return item.previousActiveRoute != nil
return item.previousActiveRoute != nil && (item.previousActiveRoute.IsActive() || item.previousActiveRoute.IsDestroying())
}

// SetupTriggers installs needed triggers for the given SignalItem, with the
Expand Down
45 changes: 36 additions & 9 deletions simulation/simulation.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import (
log "gopkg.in/inconshreveable/log15.v2"
)

const timeStep = 500 * time.Millisecond
const TimeStep = 500 * time.Millisecond

// Version of the software, mostly used for file format
const Version = "0.7"
Expand Down Expand Up @@ -62,9 +62,11 @@ type Simulation struct {
MessageLogger *MessageLogger
EventChan chan *Event

clockTicker *time.Ticker
stopChan chan bool
started bool
clockTicker *time.Ticker
stopChan chan bool
activatedRoutesChan chan *Route
deactivatedRoutesChan chan *Route
started bool
}

// UnmarshalJSON for the Simulation type
Expand All @@ -84,6 +86,8 @@ func (sim *Simulation) UnmarshalJSON(data []byte) error {

sim.EventChan = make(chan *Event)
sim.stopChan = make(chan bool)
sim.activatedRoutesChan = make(chan *Route)
sim.deactivatedRoutesChan = make(chan *Route)

var rawSim auxSim
if err := json.Unmarshal(data, &rawSim); err != nil {
Expand Down Expand Up @@ -292,7 +296,7 @@ func (sim *Simulation) Start() {

// run enters the main loop of the simulation
func (sim *Simulation) run() {
clockTicker := time.NewTicker(timeStep)
clockTicker := time.NewTicker(TimeStep)
for {
select {
case <-sim.stopChan:
Expand All @@ -301,9 +305,10 @@ func (sim *Simulation) run() {
Logger.Info("Simulation paused")
return
case <-clockTicker.C:
sim.increaseTime(timeStep)
sim.sendEvent(&Event{Name: ClockEvent, Object: sim.Options.CurrentTime})
sim.increaseTime(TimeStep)
sim.sendEvent(&Event{Name: ClockEvent, Object: sim.CurrentTime()})
sim.updateTrains()
sim.updateRoutes()
}
}
}
Expand Down Expand Up @@ -332,6 +337,13 @@ func (sim *Simulation) increaseTime(step time.Duration) {
sim.Options.CurrentTime = sim.Options.CurrentTime.Add(time.Duration(sim.Options.TimeFactor) * step)
}

// CurrentTime returns the current time in a concurrently safe manner
func (sim *Simulation) CurrentTime() Time {
sim.Options.CurrentTime.RLock()
defer sim.Options.CurrentTime.RUnlock()
return sim.Options.CurrentTime
}

// checks that all TrackItems are linked together.
// Returns the first error met.
func (sim *Simulation) checkTrackItemsLinks() error {
Expand Down Expand Up @@ -371,11 +383,11 @@ func (sim *Simulation) checkTrackItemsLinks() error {
// updateTrains update all trains information such as status, position, speed, etc.
func (sim *Simulation) updateTrains() {
for _, train := range sim.Trains {
train.activate(sim.Options.CurrentTime)
train.activate(sim.CurrentTime())
if !train.IsActive() {
continue
}
train.advance(timeStep * time.Duration(sim.Options.TimeFactor))
train.advance(TimeStep * time.Duration(sim.Options.TimeFactor))
}
}

Expand All @@ -388,6 +400,21 @@ func (sim *Simulation) updateScore(penalty int) {
})
}

// updateRoutes updates the routes that have been (de)activated
func (sim *Simulation) updateRoutes() {
mainLoop:
for {
select {
case route := <-sim.activatedRoutesChan:
route.doActivate()
case route := <-sim.deactivatedRoutesChan:
route.doDeactivate()
default:
break mainLoop
}
}
}

// RegisterRoutesManager registers the given route manager in the simulation.
//
// When several routes managers are registered, all of them are called in turn.
Expand Down
Loading