diff --git a/McZapkie/hamulce.cpp b/McZapkie/hamulce.cpp index 44e8be4b..86a22c46 100644 --- a/McZapkie/hamulce.cpp +++ b/McZapkie/hamulce.cpp @@ -588,6 +588,21 @@ int TBrake::GetSoundFlag() return result; } +/// Returns the accumulated SoundFlag bitfield and leaves it alone. Reading the flags is +/// what clears them, so anything that only watches - the network layer, for one - has to +/// look this way or it steals the sound from the vehicle it is watching. +int TBrake::PeekSoundFlag() const +{ + return SoundFlag; +} + +/// Adds events to the pending bitfield. Used to hand a vehicle the brake sounds its +/// physics did not produce here because somebody else is running it. +void TBrake::RaiseSoundFlag(int const Flags) +{ + SoundFlag |= Flags; +} + /// Sets the anti-slip brake target pressure. /// Pressure [bar]. void TBrake::SetASBP(double const Press) diff --git a/McZapkie/hamulce.h b/McZapkie/hamulce.h index a05a3635..ea03c1b0 100644 --- a/McZapkie/hamulce.h +++ b/McZapkie/hamulce.h @@ -481,6 +481,12 @@ class TBrake virtual void ForceLeak(double const Amount); /// Returns and clears the accumulated SoundFlag bitfield. int GetSoundFlag(); + /// Returns the accumulated SoundFlag bitfield without clearing it, for an + /// observer that must not consume what the vehicle itself is about to play. + int PeekSoundFlag() const; + /// Adds events to the pending SoundFlag bitfield, so that what happened on + /// another peer is heard here as well. + void RaiseSoundFlag(int const Flags); /// Returns the current brake status flags. int GetBrakeStatus() const { diff --git a/application/drivermode.cpp b/application/drivermode.cpp index e252f4c6..c77ebf82 100644 --- a/application/drivermode.cpp +++ b/application/drivermode.cpp @@ -30,6 +30,7 @@ http://mozilla.org/MPL/2.0/. #include "rendering/renderer.h" #include "utilities/Logs.h" #include "network/entities.h" +#include "network/snapshot.h" /* namespace input { @@ -449,6 +450,11 @@ bool driver_mode::update() Timer::subsystem.sim_total.stop(); + // what the other peers' trains are doing is theirs to decide, and the local physics + // has spent this frame disagreeing about it. put their readings back before anything + // is heard or drawn from them + network::hold_remote_state(); + simulation::Region->update_sounds(); audio::renderer.update(Global.iPause ? 0.0 : deltarealtime); diff --git a/network/snapshot.cpp b/network/snapshot.cpp index 7734fa7e..6fdc87c8 100644 --- a/network/snapshot.cpp +++ b/network/snapshot.cpp @@ -79,6 +79,11 @@ TTrack *track_by_id(uint32_t const Id) // one-shot sound events seen since the last update went out, per vehicle std::unordered_map g_soundevents; +// the same for the brake unit's own events, plus what was on its bitfield the last time +// we looked - the flags are only cleared when the vehicle has an accelerator sound to +// play, so a bit that stays lit is one event, not one per frame +std::unordered_map g_brakesoundevents; +std::unordered_map g_brakesoundseen; std::unordered_map g_lastvehicles; std::unordered_map g_lastmemcells; @@ -221,6 +226,19 @@ struct vehicle_state bool compressorlock{false}; bool releaser{false}; int32_t soundevents{0}; + // the brake unit keeps its own event bitfield, and it drives the accelerator and the + // cylinder venting + int32_t brakesoundevents{0}; + // outputs of the physics, not settings. a peer that does not run this train computes + // its own and gets them wrong, so the authority's values are what it is told to use: + // these are what the braking, squealing and relay sounds are built from + double unitbrakeforce{0.0}; + int32_t mainctrlactual{0}; + int32_t scndctrlactual{0}; + bool linecontactors{false}; + bool slippingwheels{false}; + bool sanddose{false}; + double enginepower{0.0}; int32_t emergencywarningsignal{0}; bool alarmchain{false}; double wheelrevolutions{0.0}; @@ -231,6 +249,10 @@ struct vehicle_state bool aiactive{false}; }; +// the authoritative reading for every train somebody else runs, held so that it can be +// put back after each local physics step +std::unordered_map g_remotestate; + void serialize_vehicle(std::ostream &Stream, vehicle_state const &State) { sn_utils::s_str(Stream, State.name); @@ -273,6 +295,14 @@ void serialize_vehicle(std::ostream &Stream, vehicle_state const &State) sn_utils::s_bool(Stream, State.compressorlock); sn_utils::s_bool(Stream, State.releaser); sn_utils::ls_int32(Stream, State.soundevents); + sn_utils::ls_int32(Stream, State.brakesoundevents); + sn_utils::ls_float64(Stream, State.unitbrakeforce); + sn_utils::ls_int32(Stream, State.mainctrlactual); + sn_utils::ls_int32(Stream, State.scndctrlactual); + sn_utils::s_bool(Stream, State.linecontactors); + sn_utils::s_bool(Stream, State.slippingwheels); + sn_utils::s_bool(Stream, State.sanddose); + sn_utils::ls_float64(Stream, State.enginepower); sn_utils::ls_int32(Stream, State.warningsignal); sn_utils::ls_int32(Stream, State.emergencywarningsignal); sn_utils::s_bool(Stream, State.alarmchain); @@ -330,6 +360,14 @@ vehicle_state deserialize_vehicle(std::istream &Stream) state.compressorlock = sn_utils::d_bool(Stream); state.releaser = sn_utils::d_bool(Stream); state.soundevents = sn_utils::ld_int32(Stream); + state.brakesoundevents = sn_utils::ld_int32(Stream); + state.unitbrakeforce = sn_utils::ld_float64(Stream); + state.mainctrlactual = sn_utils::ld_int32(Stream); + state.scndctrlactual = sn_utils::ld_int32(Stream); + state.linecontactors = sn_utils::d_bool(Stream); + state.slippingwheels = sn_utils::d_bool(Stream); + state.sanddose = sn_utils::d_bool(Stream); + state.enginepower = sn_utils::ld_float64(Stream); state.warningsignal = sn_utils::ld_int32(Stream); state.emergencywarningsignal = sn_utils::ld_int32(Stream); state.alarmchain = sn_utils::d_bool(Stream); @@ -393,6 +431,16 @@ vehicle_state read_from(TDynamicObject const &Vehicle) auto const events = g_soundevents.find(Vehicle.name()); state.soundevents = (events != g_soundevents.end() ? events->second : 0); + auto const brakeevents = g_brakesoundevents.find(Vehicle.name()); + state.brakesoundevents = (brakeevents != g_brakesoundevents.end() ? brakeevents->second : 0); + + state.unitbrakeforce = mover.UnitBrakeForce; + state.mainctrlactual = mover.MainCtrlActualPos; + state.scndctrlactual = mover.ScndCtrlActualPos; + state.linecontactors = mover.StLinFlag; + state.slippingwheels = mover.SlippingWheels; + state.sanddose = mover.SandDose; + state.enginepower = mover.EnginePower; state.warningsignal = mover.WarningSignal; state.emergencywarningsignal = mover.EmergencyBrakeWarningSignal; @@ -409,6 +457,41 @@ vehicle_state read_from(TDynamicObject const &Vehicle) return state; } +// the readings the sound is built from. these are results of a physics step, and a peer +// that is not running this train produces its own, wrong, version of them every frame - +// which is exactly what made the brakes and the squeal come out in shreds, the pitch +// chasing a speed that fell away between updates and snapped back on the next one. +// so they are taken from the authority and put back after every local step +void apply_outputs(TDynamicObject &Vehicle, vehicle_state const &State) +{ + auto &mover = *Vehicle.MoverParameters; + + mover.V = State.velocity; + mover.Vel = State.velocitykmh; + mover.UnitBrakeForce = State.unitbrakeforce; + mover.nrot = State.wheelrevolutions; + mover.enrot = State.enginerevolutions; + mover.EnginePower = State.enginepower; + mover.SlippingWheels = State.slippingwheels; + mover.SandDose = State.sanddose; + + // the notch the resistors are actually on, as opposed to the one the handle is at. + // with this coming from the authority the local automatic notching stays out of it, + // and every relay that clicks here is one that clicked over there + mover.MainCtrlActualPos = State.mainctrlactual; + mover.ScndCtrlActualPos = State.scndctrlactual; + mover.StLinFlag = State.linecontactors; + + // the machinery whose sound runs for as long as it does. left to itself the local + // physics keeps switching these off, because the conditions it evaluates are not the + // ones the owner of the train is evaluating + mover.ConverterFlag = State.converterrunning; + mover.CompressorFlag = State.compressorrunning; + mover.PantCompFlag = State.pantcompressorrunning; + mover.CompressorGovernorLock = State.compressorlock; + mover.WarningSignal = State.warningsignal; +} + // applies everything except where the vehicle is; position is dealt with per consist, // because moving one vehicle of a coupled set on its own tears the couplers apart. // only ever called for vehicles somebody else is running @@ -457,25 +540,20 @@ void apply_controls(TDynamicObject &Vehicle, vehicle_state const &State) mover.iLights[0] = State.lights[0]; mover.iLights[1] = State.lights[1]; - // the horn, the wheels and the engine note. these are set from a cab this peer does - // not have, so nothing but the result of them ever reaches it - mover.ConverterFlag = State.converterrunning; - mover.CompressorFlag = State.compressorrunning; - mover.PantCompFlag = State.pantcompressorrunning; - mover.CompressorGovernorLock = State.compressorlock; - if (mover.Hamulec != nullptr && mover.Hamulec->Releaser() != State.releaser) mover.Hamulec->Releaser(State.releaser ? 1 : 0); // the one-shot events are added to whatever this peer's own physics produced; the // vehicle's sound handling plays them once and wipes the lot, as it always does mover.SoundFlag |= State.soundevents; + if (mover.Hamulec != nullptr) + mover.Hamulec->RaiseSoundFlag(State.brakesoundevents); + + // and the physics outputs the sounds are shaped from + apply_outputs(Vehicle, State); - mover.WarningSignal = State.warningsignal; mover.EmergencyBrakeWarningSignal = State.emergencywarningsignal; mover.AlarmChainFlag = State.alarmchain; - mover.nrot = State.wheelrevolutions; - mover.enrot = State.enginerevolutions; for (int i = 0; i < 2; ++i) { @@ -579,6 +657,7 @@ std::string write_vehicles(bool const Full, bool const OwnedOnly) // the one-shot events have been handed over; they must not go out twice g_soundevents.erase(state.name); + g_brakesoundevents.erase(state.name); } if (count == 0) @@ -622,6 +701,9 @@ void read_vehicles(std::istream &Stream, network::snapshot_mode const Mode, netw apply_controls(*vehicle, state); states.emplace(vehicle, state); + // kept so that the local physics can be overruled again on every frame until the + // next update arrives, not only on the frame it lands + g_remotestate[state.name] = state; Result.worst_position_error = std::max(Result.worst_position_error, glm::length(vehicle->GetPosition() - state.position)); } @@ -882,9 +964,46 @@ void network::note_sound_events(std::string const &Vehicle, int const Events) g_soundevents[Vehicle] |= Events; } +void network::note_brake_sound_events(std::string const &Vehicle, int const Events) +{ + // what comes in here was peeked, not consumed, so a flag the vehicle never gets round + // to clearing would otherwise be reported over and over. only what has just come up + // counts as an event + auto &seen = g_brakesoundseen[Vehicle]; + int const fresh = Events & ~seen; + seen = Events; + + if (fresh == 0) + return; + + g_brakesoundevents[Vehicle] |= fresh; +} + +void network::hold_remote_state() +{ + if (g_remotestate.empty()) + return; + + for (auto &entry : g_remotestate) + { + TDynamicObject *vehicle = simulation::Vehicles.find(entry.first); + if (vehicle == nullptr || vehicle->MoverParameters == nullptr) + continue; + + // a train that has come under this peer's own control runs its own physics again + if (network::is_locally_simulated(network::Entities.id_of(entry.first))) + continue; + + apply_outputs(*vehicle, entry.second); + } +} + void network::reset_snapshot_history() { g_soundevents.clear(); + g_brakesoundevents.clear(); + g_brakesoundseen.clear(); + g_remotestate.clear(); g_lastvehicles.clear(); g_lastmemcells.clear(); g_lastswitches.clear(); diff --git a/network/snapshot.h b/network/snapshot.h index 963072d5..b42eec3b 100644 --- a/network/snapshot.h +++ b/network/snapshot.h @@ -18,7 +18,7 @@ namespace network { // layout of the snapshot container. a reader refuses a blob it does not understand -constexpr uint32_t SNAPSHOT_VERSION = 4; +constexpr uint32_t SNAPSHOT_VERSION = 5; // the sections a snapshot is built from. a reader skips over any id it does not know, // which is what makes the format extensible: a peer built before a section existed can @@ -77,4 +77,15 @@ void reset_snapshot_history(); // is called from the vehicle's own sound handling, just before it wipes them void note_sound_events(std::string const &Vehicle, int Events); +// same, for the events the brake unit raises. those are only cleared when the vehicle +// happens to have an accelerator sound to play, so what is passed here is peeked rather +// than consumed and only the flags that have just appeared are taken +void note_brake_sound_events(std::string const &Vehicle, int Events); + +// re-asserts, on every frame, the physics outputs of the vehicles somebody else is +// running. an update arrives ten times a second; between them the local physics carries +// on computing its own speed and brake force for a train it has no business simulating, +// and the sawtooth that produces is what the brake and squeal sounds were chasing +void hold_remote_state(); + } // namespace network diff --git a/vehicle/DynObj.cpp b/vehicle/DynObj.cpp index aec91198..014edd36 100644 --- a/vehicle/DynObj.cpp +++ b/vehicle/DynObj.cpp @@ -4626,6 +4626,12 @@ void TDynamicObject::RenderSounds() { } // yB: przyspieszacz (moze zadziala, ale dzwiek juz jest) + if( ( true == network::is_multiplayer() ) + && ( MoverParameters->Hamulec != nullptr ) ) { + // peeked rather than read: reading the brake unit's flags is what clears them, + // and they belong to the vehicle, not to us + network::note_brake_sound_events( name(), MoverParameters->Hamulec->PeekSoundFlag() ); + } if( true == bBrakeAcc ) { if( true == TestFlag( MoverParameters->Hamulec->GetSoundFlag(), sf_Acc ) ) { sBrakeAcc.play( sound_flags::exclusive );