/// <summary> /// Constructs a new CALExemption. /// </summary> /// <param name="schema">The schema object containing data for this CAL exemption.</param> /// <param name="region">The region.</param> /// <param name="subArea">The ID number of the sub-area against which to resolve short object IDs.</param> public CALExemption(Schema.CALExemption schema, Region region, ushort subArea) { { TrackCircuit[] tcs = new TrackCircuit[schema.ClearTCs.Count]; for (int i = 0; i != schema.ClearTCs.Count; ++i) { tcs[i] = region.GetTrackCircuit(schema.ClearTCs[i], subArea); } ClearTCs = Array.AsReadOnly(tcs); } { RoutePointPosition[] pointPositions = new RoutePointPosition[schema.PointPositions.Count]; for (int i = 0; i != schema.PointPositions.Count; ++i) { pointPositions[i] = new RoutePointPosition(schema.PointPositions[i], region, subArea); } PointPositions = Array.AsReadOnly(pointPositions); } { Signal[] sigs = new Signal[schema.SignalsOn.Count]; for (int i = 0; i != schema.SignalsOn.Count; ++i) { sigs[i] = region.GetSignal(schema.SignalsOn[i], subArea); } SignalsOn = Array.AsReadOnly(sigs); } }
/// <summary> /// Constructs a route from its spec. /// </summary> /// <param name="schema">The schema object containing data about this route.</param> /// <param name="region">The region the route exists in.</param> /// <param name="entrance">The signal at which this route begins.</param> public Route(Schema.Route schema, Region region, ControlledSignal entrance) { Entrance = entrance; Exit = region.GetSignal(schema.Exit, entrance.SubArea); Divergence = schema.Divergence; Restricting = schema.Restricting; DivergenceDistanceStraightOnly = schema.DivergenceDistanceStraightOnly; { RouteElement[] elts = new RouteElement[schema.TCs.Count]; for (int i = 0; i != schema.TCs.Count; ++i) { elts[i] = new RouteElement(schema.TCs[i], region, entrance.SubArea); } Elements = Array.AsReadOnly(elts); } { RoutePointPosition[] elts = new RoutePointPosition[schema.Points.Count]; for (int i = 0; i != schema.Points.Count; ++i) { elts[i] = new RoutePointPosition(schema.Points[i], region, entrance.SubArea); } PointPositions = Array.AsReadOnly(elts); } { TrackCircuit[] tcs = new TrackCircuit[schema.FreeTCs.Count]; for (int i = 0; i != schema.FreeTCs.Count; ++i) { tcs[i] = region.GetTrackCircuit(schema.FreeTCs[i], entrance.SubArea); } FreeTrackCircuits = Array.AsReadOnly(tcs); } }
/// <summary> /// Calls the route. /// </summary> /// <remarks> /// The caller must verify that the route is available first. /// </remarks> public async Task CallAsync() { Debug.Assert(Available); // Lock the track circuits. for (int i = 0; i != Elements.Count; ++i) { TrackCircuit current = Elements[i].TrackCircuit; TrackCircuit next = i + 1 < Elements.Count ? Elements[i + 1].TrackCircuit : null; current.RouteLock(Elements[i].Direction, next, this); } // Set the signal's route. Entrance.SetCurrentRoute(this); // Swing and lock the points. Task[] tasks = new Task[PointPositions.Count]; for (int i = 0; i != PointPositions.Count; ++i) { tasks[i] = PointPositions[i].Points.SwingAsync(PointPositions[i].Reverse); } foreach (Task i in tasks) { await i; } }
/// <summary> /// Locks this track circuit as part of a route. /// </summary> /// <param name="direction">The direction in which the track circuit is to be locked.</param> /// <param name="next">The next track circuit in the route, <c>null</c> if this is the last track circuit.</param> /// <param name="route">The route being locked.</param> public void RouteLock(char direction, TrackCircuit next, Route route) { Debug.Assert(!RouteLocked || RouteLockedDirection == direction); Debug.Assert(route != null); RouteLockedDirection = direction; NextInRoute = next; LockedRoute = route; RouteLockCascaded = true; }
/// <summary> /// Finds a track circuit. /// </summary> /// <param name="id">The ID of the track circuit, which can be long or short.</param> /// <param name="subAreaID">The ID of the sub-area containing the object doing the lookup, which will be used to resolve a short TC ID.</param> /// <returns>The track circuit.</returns> public TrackCircuit GetTrackCircuit(uint id, ushort subAreaID) { if (id >= 1000) { Debug.Assert(id < 1000000); subAreaID = (ushort)(id / 1000); id %= 1000; } TrackCircuit tc = SubAreas[subAreaID].TrackCircuits[(ushort)id]; Debug.Assert(tc != null); return(tc); }
/// <summary> /// Constructs a new sub-area. /// </summary> /// <param name="schema">Data about this sub-area.</param> /// <param name="id">The sub-area ID.</param> /// <param name="world">The communication interface to Run8.</param> public SubArea(Schema.SubArea schema, ushort id, World world) { ID = id; Name = schema.Name ?? string.Empty; { ushort maxID = 0; foreach (Schema.TrackCircuit i in schema.TrackCircuits) { maxID = Math.Max(maxID, i.ID); } TrackCircuit[] trackCircuits = new TrackCircuit[maxID + 1]; foreach (Schema.TrackCircuit i in schema.TrackCircuits) { Debug.Assert(trackCircuits[i.ID] == null); trackCircuits[i.ID] = new TrackCircuit(i, ID, i.ID); } TrackCircuits = Array.AsReadOnly(trackCircuits); } { Points[] points = new Points[schema.Points.Count]; for (ushort i = 0; i != schema.Points.Count; ++i) { points[i] = new Points(schema.Points[i], i, this, world); } PowerPoints = Array.AsReadOnly(points); } { ControlledSignal[] sigs = new ControlledSignal[schema.ControlledSignals.Count]; for (ushort i = 0; i != schema.ControlledSignals.Count; ++i) { sigs[i] = new ControlledSignal(schema.ControlledSignals[i], i, ID, world); } ControlledSignals = Array.AsReadOnly(sigs); } { AutomaticSignal[] sigs = new AutomaticSignal[schema.AutomaticSignals.Count]; for (ushort i = 0; i != schema.AutomaticSignals.Count; ++i) { sigs[i] = new AutomaticSignal(schema.AutomaticSignals[i], (short)(-i - 1), ID); } AutomaticSignals = Array.AsReadOnly(sigs); } Signals = new SignalsArray(this); }
/// <summary> /// Constructs a Points. /// </summary> /// <param name="schema">The schema object containing data about these points.</param> /// <param name="id">The Run8 internal ID number of these points.</param> /// <param name="subArea">The sub-area that contains these points.</param> /// <param name="world">The containing world.</param> public Points(Schema.Points schema, ushort id, SubArea subArea, World world) { Debug.Assert(id >= 0); Debug.Assert(world != null); SubArea = subArea.ID; ID = id; World = world; OccupiedImpl = true; ProvedImpl = false; { TrackCircuit[] protectingTCs = new TrackCircuit[schema.ProtectingTCs.Count]; for (int i = 0; i != schema.ProtectingTCs.Count; ++i) { protectingTCs[i] = subArea.TrackCircuits[schema.ProtectingTCs[i]]; protectingTCs[i].PropertyChanged += OnTCPropChanged; } ProtectingTCs = Array.AsReadOnly(protectingTCs); } }
/// <summary> /// Accepts a packet from Run8. /// </summary> /// <param name="data">The data packet.</param> public void UpdateFromRun8(TrainData data) { // Sanity check. Debug.Assert(data.TrainID == ID); // Timestamp the new arrival. DataLastUpdated = DateTime.UtcNow; // Update general properties. Company = data.RailroadInitials; LocoNumber = data.LocoNumber; Tag = data.TrainSymbol; Speed = (int)data.TrainSpeedMph; SpeedLimit = data.TrainSpeedLimitMPH; EngineerType = data.EngineerType; EngineerName = (EngineerType == EEngineerType.AI) ? "AI" : (EngineerType == EEngineerType.None) ? "No Driver" : data.EngineerName; Length = data.TrainLengthFeet; Weight = data.TrainWeightTons; HPt = data.HpPerTon; // Update AI hold/relinquish orders. We don't use the property setters here because the property setters send order packets to Run8, as they are intended for invocation by the signaller. Instead set the backing fields emit property change notifications directly. SetProperty(ref AIHoldImpl, data.HoldingForDispatcher, nameof(AIHold)); SetProperty(ref AIRelinquishImpl, data.RelinquishWhenStopped, nameof(AIRelinquish)); // Update location, keeping the old string if not available. SubArea sub = null; TrackCircuit tc = null; if (data.BlockID >= 0) { ushort subAreaID, tcID; if (data.BlockID >= 100000) { subAreaID = (ushort)(data.BlockID / 1000); tcID = (ushort)(data.BlockID % 1000); } else if (data.BlockID >= 10000) { subAreaID = (ushort)(data.BlockID / 100); tcID = (ushort)(data.BlockID % 100); } else { subAreaID = (ushort)(data.BlockID / 10); tcID = (ushort)(data.BlockID % 10); } if (World.Region.SubAreas.TryGetValue(subAreaID, out sub)) { if (tcID < sub.TrackCircuits.Count) { tc = sub.TrackCircuits[tcID]; } } } if (tc != null) { SubArea = sub.Name; Location = tc.LocationName; } LocationCurrent = tc != null; // Fix up which track circuit we are in. TrackCircuit berth = tc?.GetBerth(); if (berth != TrackCircuit) { if (TrackCircuit != null) { TrackCircuit.Trains.Remove(this); } TrackCircuit = berth; if (TrackCircuit != null) { TrackCircuit.Trains.Add(this); } } }