diff --git a/Classes.h b/Classes.h index 3e0bc8e6..f91c5e08 100644 --- a/Classes.h +++ b/Classes.h @@ -40,6 +40,7 @@ class memory_table; class powergridsource_table; class instance_table; class vehicle_table; +class train_table; struct light_array; class particle_manager; struct dictionary_source; @@ -77,4 +78,8 @@ enum class TCommandType using material_handle = int; using texture_handle = int; +struct invalid_scenery_exception : std::runtime_error { + invalid_scenery_exception() : std::runtime_error("cannot load scenery") {} +}; + #endif diff --git a/Driver.cpp b/Driver.cpp index 39d65109..1e32858b 100644 --- a/Driver.cpp +++ b/Driver.cpp @@ -1862,11 +1862,9 @@ void TController::Activation() } if (pVehicle != old) { // jeśli zmieniony został pojazd prowadzony - if( ( simulation::Train ) - && ( simulation::Train->Dynamic() == old ) ) { - // ewentualna zmiana kabiny użytkownikowi - Global.changeDynObj = pVehicle; // uruchomienie protezy - } + TTrain *train = simulation::Trains.find(old->name()); + if (train) + train->MoveToVehicle(pVehicle); ControllingSet(); // utworzenie połączenia do sterowanego pojazdu (może się zmienić) - silnikowy dla EZT } if( mvControlling->EngineType == TEngineType::DieselEngine ) { diff --git a/DynObj.cpp b/DynObj.cpp index af7cc565..625d94da 100644 --- a/DynObj.cpp +++ b/DynObj.cpp @@ -1615,6 +1615,36 @@ TDynamicObject::~TDynamicObject() { SafeDeleteArray( pAnimated ); // lista animowanych submodeli } +void TDynamicObject::place_on_track(TTrack *Track, double fDist, bool Reversed) +{ + for( auto &axle : m_axlesounds ) { + // wyszukiwanie osi (0 jest na końcu, dlatego dodajemy długość?) + axle.distance = ( + Reversed ? + -axle.offset : + ( axle.offset + MoverParameters->Dim.L ) ) + fDist; + } + double fAxleDistHalf = fAxleDist * 0.5; + // przesuwanie pojazdu tak, aby jego początek był we wskazanym miejcu + fDist -= 0.5 * MoverParameters->Dim.L; // dodajemy pół długości pojazdu, bo ustawiamy jego środek (zliczanie na minus) + switch (iNumAxles) { + // Ra: pojazdy wstawiane są na tor początkowy, a potem przesuwane + case 2: // ustawianie osi na torze + Axle0.Init(Track, this, iDirection ? 1 : -1); + Axle0.Reset(); + Axle0.Move((iDirection ? fDist : -fDist) + fAxleDistHalf, false); + Axle1.Init(Track, this, iDirection ? 1 : -1); + Axle1.Reset(); + Axle1.Move((iDirection ? fDist : -fDist) - fAxleDistHalf, false); // false, żeby nie generować eventów + break; + } + // potrzebne do wyliczenia aktualnej pozycji; nie może być zero, bo nie przeliczy pozycji + // teraz jeszcze trzeba przypisać pojazdy do nowego toru, bo przesuwanie początkowe osi nie + // zrobiło tego + Move( 0.0001 ); + ABuCheckMyTrack(); // zmiana toru na ten, co oś Axle0 (oś z przodu) +} + double TDynamicObject::Init(std::string Name, // nazwa pojazdu, np. "EU07-424" std::string BaseDir, // z którego katalogu wczytany, np. "PKP/EU07" @@ -2190,7 +2220,7 @@ TDynamicObject::create_controller( std::string const Type, bool const Trainset ) if( Type == "" ) { return; } - if( asName == Global.asHumanCtrlVehicle ) { + if( asName == Global.local_start_vehicle ) { // jeśli pojazd wybrany do prowadzenia if( MoverParameters->EngineType != TEngineType::Dumb ) { // wsadzamy tam sterującego @@ -6542,6 +6572,20 @@ glm::dvec3 TDynamicObject::get_future_movement() const { return m_future_movement; } +void TDynamicObject::move_set(double distance) +{ + TDynamicObject *d = this; + while( d ) { + d->Move( distance * d->DirectionGet() ); + d = d->Next(); // pozostałe też + } + d = Prev(); + while( d ) { + d->Move( distance * d->DirectionGet() ); + d = d->Prev(); // w drugą stronę też + } +} + // returns type of the nearest functional power source present in the trainset TPowerSource TDynamicObject::ConnectedEnginePowerSource( TDynamicObject const *Caller ) const { @@ -6640,13 +6684,13 @@ TDynamicObject::update_shake( double const Timedelta ) { auto shake { 1.25 * ShakeSpring.ComputateForces( shakevector, ShakeState.offset ) }; - if( Random( iVel ) > 25.0 ) { + if( LocalRandom( iVel ) > 25.0 ) { // extra shake at increased velocity shake += ShakeSpring.ComputateForces( Math3D::vector3( - ( Random( iVel * 2 ) - iVel ) / ( ( iVel * 2 ) * 4 ) * BaseShake.jolt_scale.x, - ( Random( iVel * 2 ) - iVel ) / ( ( iVel * 2 ) * 4 ) * BaseShake.jolt_scale.y, - ( Random( iVel * 2 ) - iVel ) / ( ( iVel * 2 ) * 4 ) * BaseShake.jolt_scale.z ) + ( LocalRandom( iVel * 2 ) - iVel ) / ( ( iVel * 2 ) * 4 ) * BaseShake.jolt_scale.x, + ( LocalRandom( iVel * 2 ) - iVel ) / ( ( iVel * 2 ) * 4 ) * BaseShake.jolt_scale.y, + ( LocalRandom( iVel * 2 ) - iVel ) / ( ( iVel * 2 ) * 4 ) * BaseShake.jolt_scale.z ) // * (( 200 - DynamicObject->MyTrack->iQualityFlag ) * 0.0075 ) // scale to 75-150% based on track quality * 1.25, ShakeState.offset ); @@ -6986,7 +7030,7 @@ TDynamicObject::powertrain_sounds::render( TMoverParameters const &Vehicle, doub if( ( volume < 1.0 ) && ( Vehicle.EnginePower < 100 ) ) { - auto const volumevariation { Random( 100 ) * Vehicle.enrot / ( 1 + Vehicle.nmax ) }; + auto const volumevariation { LocalRandom( 100 ) * Vehicle.enrot / ( 1 + Vehicle.nmax ) }; if( volumevariation < 2 ) { volume += volumevariation / 200; } @@ -7362,6 +7406,7 @@ vehicle_table::erase_disabled() { // TBD, TODO: kill vehicle sounds SafeDelete( simulation::Train ); } + simulation::Trains.purge(vehicle->name()); // remove potential entries in the light array simulation::Lights.remove( vehicle ); /* diff --git a/DynObj.h b/DynObj.h index b61d9620..efb3dac1 100644 --- a/DynObj.h +++ b/DynObj.h @@ -544,6 +544,7 @@ private: TDynamicObject(); ~TDynamicObject(); + void place_on_track(TTrack *Track, double fDist, bool Reversed); // zwraca długość pojazdu albo 0, jeśli błąd double Init( std::string Name, std::string BaseDir, std::string asReplacableSkin, std::string Type_Name, @@ -672,6 +673,7 @@ private: material_handle DestinationFind( std::string Destination ); void OverheadTrack(float o); glm::dvec3 get_future_movement() const; + void move_set(double distance); double MED[9][8]; // lista zmiennych do debugowania hamulca ED static std::string const MED_labels[ 8 ]; diff --git a/EvLaunch.cpp b/EvLaunch.cpp index 275be42d..fc411b30 100644 --- a/EvLaunch.cpp +++ b/EvLaunch.cpp @@ -31,20 +31,23 @@ http://mozilla.org/MPL/2.0/. // encodes expected key in a short, where low byte represents the actual key, // and the high byte holds modifiers: 0x1 = shift, 0x2 = ctrl, 0x4 = alt int vk_to_glfw_key( int const Keycode ) { + char modifier = 0; + char key = 0; -#ifdef _WIN32 - auto const code = VkKeyScan( Keycode ); - char key = code & 0xff; - char shiftstate = ( code & 0xff00 ) >> 8; + if (Keycode < 'A') { + key = Keycode; + } else if (Keycode <= 'Z') { + key = Keycode; + modifier = GLFW_MOD_SHIFT; + } else if (Keycode < 'a') { + key = Keycode; + } else if (Keycode <= 'z') { + key = Keycode - 32; + } else { + ErrorLog("unknown key: " + std::to_string(Keycode)); + } - if( (key >= 'A') && (key <= 'Z') ) { - key = GLFW_KEY_A + key - 'A'; - } - else if( ( key >= '0' ) && ( key <= '9' ) ) { - key = GLFW_KEY_0 + key - '0'; - } - return key + ( shiftstate << 8 ); -#endif + return ((int)modifier << 8) | key; } bool TEventLauncher::Load(cParser *parser) @@ -152,24 +155,33 @@ bool TEventLauncher::Load(cParser *parser) return true; } +bool TEventLauncher::check_activation_key() { + if (iKey <= 0) + return false; + + char key = iKey & 0xff; + + bool result = Console::Pressed(key); + + char modifier = iKey >> 8; + if (modifier & GLFW_MOD_SHIFT) + result &= Global.shiftState; + if (modifier & GLFW_MOD_CONTROL) + result &= Global.ctrlState; + + return result; +} + bool TEventLauncher::check_activation() { auto bCond { false }; - if( iKey > 0 ) { - if( iKey > 255 ) { - // key and modifier - auto const modifier = ( iKey & 0xff00 ) >> 8; - bCond = ( Console::Pressed( iKey & 0xff ) ) - && ( ( modifier & 1 ) ? Global.shiftState : true ) - && ( ( modifier & 2 ) ? Global.ctrlState : true ); - } - else { - // just key - bCond = ( Console::Pressed( iKey & 0xff ) ); // czy klawisz wciśnięty - } - } - if( DeltaTime > 0 ) { + if (DeltaTime == 10000.0) { + if (UpdatedTime == 0.0) + bCond = true; + UpdatedTime = 1.0; + } + else if( DeltaTime > 0 ) { if( UpdatedTime > DeltaTime ) { UpdatedTime = 0; // naliczanie od nowa bCond = true; diff --git a/EvLaunch.h b/EvLaunch.h index b4006901..7ed9d240 100644 --- a/EvLaunch.h +++ b/EvLaunch.h @@ -30,6 +30,7 @@ public: // methods bool Load( cParser *parser ); + bool check_activation_key(); bool check_activation(); // checks conditions associated with the event. returns: true if the conditions are met bool check_conditions(); diff --git a/Event.cpp b/Event.cpp index 805d8494..4a4e0255 100644 --- a/Event.cpp +++ b/Event.cpp @@ -1966,14 +1966,19 @@ event_manager::update() { CheckQuery(); // test list of global events for possible new additions to the queue for( auto *launcher : m_launcherqueue ) { + if (launcher->check_conditions() && launcher->Event1) { + // NOTE: we're presuming global events aren't going to use event2 - if( true == ( launcher->check_activation() && launcher->check_conditions() ) ) { - // NOTE: we're presuming global events aren't going to use event2 - WriteLog( "Eventlauncher " + launcher->name() ); - if( launcher->Event1 ) { - AddToQuery( launcher->Event1, nullptr ); - } - } + if (launcher->check_activation()) { + WriteLog( "Eventlauncher: " + launcher->name() ); + AddToQuery( launcher->Event1, nullptr ); + } + + if (launcher->check_activation_key()) { + WriteLog( "Eventlauncher: " + launcher->name() ); + m_relay.post(user_command::queueevent, 0.0, 0.0, GLFW_PRESS, 0, glm::vec3(0.0f), &launcher->Event1->name()); + } + } } } diff --git a/Event.h b/Event.h index ca01c295..06f93899 100644 --- a/Event.h +++ b/Event.h @@ -77,6 +77,7 @@ public: virtual glm::dvec3 input_location() const; void group( scene::group_handle Group ); scene::group_handle group() const; + std::string const &name() const { return m_name; } // members basic_event *m_next { nullptr }; // następny w kolejce // TODO: replace with event list in the manager basic_event *m_sibling { nullptr }; // kolejny event z tą samą nazwą - od wersji 378 @@ -584,6 +585,10 @@ public: // legacy method, returns pointer to specified event, or null basic_event * FindEvent( std::string const &Name ); + inline TEventLauncher* FindEventlauncher(std::string const &Name) { + auto ptr = m_inputdrivenlaunchers.find(Name); + return ptr ? ptr : m_radiodrivenlaunchers.find(Name); + } // legacy method, inserts specified event in the event query bool AddToQuery( basic_event *Event, TDynamicObject const *Owner ); @@ -618,6 +623,7 @@ private: basic_table m_inputdrivenlaunchers; basic_table m_radiodrivenlaunchers; eventlauncher_sequence m_launcherqueue; + command_relay m_relay; }; diff --git a/Globals.cpp b/Globals.cpp index 198430a6..304fd0f2 100644 --- a/Globals.cpp +++ b/Globals.cpp @@ -52,7 +52,7 @@ global_settings::ConfigParse(cParser &Parser) { { Parser.getTokens(); - Parser >> asHumanCtrlVehicle; + Parser >> local_start_vehicle; } else if( token == "fieldofview" ) { @@ -768,6 +768,18 @@ global_settings::ConfigParse(cParser &Parser) { Parser.getTokens(1); Parser >> gfx_shadowmap_enabled; } + else if (token == "fpslimit") + { + Parser.getTokens(1); + float fpslimit; + Parser >> fpslimit; + minframetime = std::chrono::duration(1.0f / fpslimit); + } + else if (token == "randomseed") + { + Parser.getTokens(1); + Parser >> Global.random_seed; + } else if (token == "gfx.envmap.enabled") { Parser.getTokens(1); @@ -852,6 +864,29 @@ global_settings::ConfigParse(cParser &Parser) { Parser.getTokens(1); Parser >> python_mipmaps; } + else if (token == "network.server") + { + Parser.getTokens(2); + + std::string backend; + std::string conf; + Parser >> backend >> conf; + + network_servers.push_back(std::make_pair(backend, conf)); + } + else if (token == "network.client") + { + Parser.getTokens(2); + + network_client.emplace(); + Parser >> network_client->first; + Parser >> network_client->second; + } + else if (token == "execonexit") { + Parser.getTokens(1); + Parser >> exec_on_exit; + std::replace(std::begin(exec_on_exit), std::end(exec_on_exit), '_', ' '); + } /* else if (token == "crashdamage") { Parser.getTokens(1); diff --git a/Globals.h b/Globals.h index 1e4ee291..cf04b051 100644 --- a/Globals.h +++ b/Globals.h @@ -28,8 +28,11 @@ struct global_settings { bool shiftState{ false }; //m7todo: brzydko bool ctrlState{ false }; bool altState{ false }; - std::mt19937 random_engine{ std::mt19937( static_cast( std::time( NULL ) ) ) }; - std::mt19937 local_random_engine{ std::mt19937( static_cast( std::time( NULL ) ) ) }; + std::mt19937 random_engine; + std::mt19937 local_random_engine; + bool ready_to_load { false }; + std::time_t starting_timestamp = 0; // starting time, in local timezone + uint32_t random_seed = 0; TDynamicObject *changeDynObj{ nullptr };// info o zmianie pojazdu TCamera pCamera; // parametry kamery TCamera pDebugCamera; @@ -63,7 +66,7 @@ struct global_settings { std::string szTexturesDDS{ ".dds" }; // lista tekstur od DDS std::string szDefaultExt{ szTexturesDDS }; std::string SceneryFile{ "td.scn" }; - std::string asHumanCtrlVehicle{ "EU07-424" }; + std::string local_start_vehicle{ "EU07-424" }; int iConvertModels{ 0 }; // tworzenie plików binarnych bool file_binary_terrain{ true }; // enable binary terrain (de)serialization // logs @@ -188,6 +191,13 @@ struct global_settings { std::string asVersion{ "UNKNOWN" }; // z opisem // TODO: move these to relevant areas bool render_cab = true; + + std::chrono::duration minframetime {0.0f}; + + std::string fullscreen_monitor; + + bool python_mipmaps = true; + int gfx_framebuffer_width = -1; int gfx_framebuffer_height = -1; int gfx_framebuffer_fidelity = -1; @@ -202,8 +212,12 @@ struct global_settings { bool gfx_extraeffects = true; bool gfx_shadergamma = false; bool gfx_usegles = false; - std::string fullscreen_monitor; - bool python_mipmaps = true; + + std::string exec_on_exit; + + std::vector> network_servers; + std::optional> network_client; + double desync = 0.0; // methods void LoadIniFile( std::string asFileName ); diff --git a/Logs.h b/Logs.h index 0d41bffb..c4bd93ce 100644 --- a/Logs.h +++ b/Logs.h @@ -20,7 +20,7 @@ enum logtype : unsigned int { // lua = ( 1 << 4 ), material = ( 1 << 5 ), shader = ( 1 << 6 ), -// net = ( 1 << 7 ) + net = ( 1 << 7 ) }; void WriteLog( const char *str, logtype const Type = logtype::generic ); diff --git a/Names.h b/Names.h index 4f0f1089..178d82e4 100644 --- a/Names.h +++ b/Names.h @@ -38,6 +38,43 @@ public: // item with this name already exists; update mapping to point to the new one, for backward compatibility mapping.first->second = itemhandle; return false; } + void purge (std::string const &Name) + { + auto lookup = m_itemmap.find( Name ); + if (lookup == m_itemmap.end()) + return; + delete m_items[lookup->second]; + + detach(Name); + } + void detach (std::string const &Name) + { + auto lookup = m_itemmap.find( Name ); + if (lookup == m_itemmap.end()) + return; + + m_items[lookup->second] = nullptr; + // TBD, TODO: remove from m_items? + + m_itemmap.erase(lookup); + } + uint32_t find_id( std::string const &Name) const { + auto lookup = m_itemmap.find( Name ); + return ( + lookup != m_itemmap.end() ? + lookup->second : + -1 ); + } + void purge (Type_ *Item) + { + for (auto it = m_items.begin(); it != m_items.end(); it++) { + if (*it == Item) { + delete *it; + *it = nullptr; + return; + } + } + } // locates item with specified name. returns pointer to the item, or nullptr Type_ * find( std::string const &Name ) const { diff --git a/Timer.cpp b/Timer.cpp index bfbdc456..fcd038a0 100644 --- a/Timer.cpp +++ b/Timer.cpp @@ -15,13 +15,14 @@ namespace Timer { subsystem_stopwatches subsystem; -double DeltaTime, DeltaRenderTime; +double DeltaTime = 0.0, DeltaRenderTime = 0.0; double fFPS{ 0.0f }; double fLastTime{ 0.0f }; DWORD dwFrames{ 0 }; double fSimulationTime{ 0.0 }; double fSoundTimer{ 0.0 }; double fSinceStart{ 0.0 }; +double override_delta = -1.0f; double GetTime() { @@ -30,6 +31,8 @@ double GetTime() double GetDeltaTime() { // czas symulacji (stoi gdy pauza) + if (override_delta != -1.0f) + return override_delta; return DeltaTime; } @@ -38,35 +41,31 @@ double GetDeltaRenderTime() return DeltaRenderTime; } -void SetDeltaTime(double t) +void set_delta_override(double t) { - DeltaTime = t; -} - -bool GetSoundTimer() -{ // Ra: być może, by dźwięki nie modyfikowały się zbyt często, po 0.1s zeruje się ten licznik - return (fSoundTimer == 0.0f); -} - -double GetFPS() -{ - return fFPS; + override_delta = t; } void ResetTimers() { UpdateTimers( Global.iPause != 0 ); - DeltaTime = 0.1; + DeltaTime = 0.0; DeltaRenderTime = 0.0; - fSoundTimer = 0.0; -}; +} -LONGLONG fr, count, oldCount; - -void UpdateTimers(bool pause) { +uint64_t fr, count, oldCount; +void UpdateTimers(bool pause) +{ +#ifdef _WIN32 QueryPerformanceFrequency((LARGE_INTEGER *)&fr); QueryPerformanceCounter((LARGE_INTEGER *)&count); +#elif __unix__ + timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + count = (uint64_t)ts.tv_sec * 1000000000 + (uint64_t)ts.tv_nsec; + fr = 1000000000; +#endif DeltaRenderTime = double(count - oldCount) / double(fr); if (!pause) { @@ -77,15 +76,19 @@ void UpdateTimers(bool pause) { if (DeltaTime > 1.0) DeltaTime = 1.0; + + fSimulationTime += GetDeltaTime(); } else DeltaTime = 0.0; // wszystko stoi, bo czas nie płynie oldCount = count; // Keep track of the time lapse and frame count -#if _WIN32_WINNT >= _WIN32_WINNT_VISTA +#if __unix__ + double fTime = (double)(count / 1000000000); +#elif _WIN32_WINNT >= _WIN32_WINNT_VISTA double fTime = ::GetTickCount64() * 0.001f; // Get current time in seconds -#else +#elif _WIN32 double fTime = ::GetTickCount() * 0.001f; // Get current time in seconds #endif ++dwFrames; // licznik ramek @@ -96,7 +99,6 @@ void UpdateTimers(bool pause) { fLastTime = fTime; dwFrames = 0L; } - fSimulationTime += DeltaTime; }; }; // namespace timer diff --git a/Timer.h b/Timer.h index 2aa69728..b87e473b 100644 --- a/Timer.h +++ b/Timer.h @@ -16,11 +16,7 @@ double GetTime(); double GetDeltaTime(); double GetDeltaRenderTime(); -void SetDeltaTime(double v); - -bool GetSoundTimer(); - -double GetFPS(); +void set_delta_override(double v); void ResetTimers(); @@ -35,17 +31,23 @@ public: void start() { m_start = std::chrono::steady_clock::now(); } - void + std::chrono::duration stop() { - m_accumulator = 0.95f * m_accumulator + std::chrono::duration_cast( ( std::chrono::steady_clock::now() - m_start ) ).count() / 1000.f; } + m_last = std::chrono::duration_cast( ( std::chrono::steady_clock::now() - m_start ) ); + m_accumulator = 0.95f * m_accumulator + m_last.count() / 1000.f; + return m_last; } float average() const { return m_accumulator / 20.f;} + std::chrono::microseconds + last() const { + return m_last; } private: // members std::chrono::time_point m_start { std::chrono::steady_clock::now() }; float m_accumulator { 1000.f / 30.f * 20.f }; // 20 last samples, initial 'neutral' rate of 30 fps + std::chrono::microseconds m_last; }; struct subsystem_stopwatches { diff --git a/Train.cpp b/Train.cpp index 6a5dc5ba..d828d438 100644 --- a/Train.cpp +++ b/Train.cpp @@ -381,6 +381,9 @@ TTrain::commandhandler_map const TTrain::m_commandhandlers = { { user_command::generictoggle7, &TTrain::OnCommand_generictoggle }, { user_command::generictoggle8, &TTrain::OnCommand_generictoggle }, { user_command::generictoggle9, &TTrain::OnCommand_generictoggle }, + { user_command::vehiclemoveforwards, &TTrain::OnCommand_vehiclemoveforwards }, + { user_command::vehiclemovebackwards, &TTrain::OnCommand_vehiclemovebackwards }, + { user_command::vehicleboost, &TTrain::OnCommand_vehicleboost }, { user_command::springbraketoggle, &TTrain::OnCommand_springbraketoggle }, { user_command::springbrakeenable, &TTrain::OnCommand_springbrakeenable }, { user_command::springbrakedisable, &TTrain::OnCommand_springbrakedisable }, @@ -5582,14 +5585,15 @@ void TTrain::OnCommand_cabchangeforward( TTrain *Train, command_data const &Comm end::rear ) }; if( TestFlag( Train->DynamicObject->MoverParameters->Couplers[ exitdirection ].CouplingFlag, coupling::gangway ) ) { // przejscie do nastepnego pojazdu - Global.changeDynObj = ( + auto *targetvehicle = ( exitdirection == end::front ? Train->DynamicObject->PrevConnected() : Train->DynamicObject->NextConnected() ); - Global.changeDynObj->MoverParameters->CabOccupied = ( + targetvehicle->MoverParameters->CabOccupied = ( Train->DynamicObject->MoverParameters->Neighbours[ exitdirection ].vehicle_end ? -1 : 1 ); + Train->MoveToVehicle( targetvehicle ); } } // HACK: match consist door permit state with the preset in the active cab @@ -5615,14 +5619,15 @@ void TTrain::OnCommand_cabchangebackward( TTrain *Train, command_data const &Com end::rear ) }; if( TestFlag( Train->DynamicObject->MoverParameters->Couplers[ exitdirection ].CouplingFlag, coupling::gangway ) ) { // przejscie do nastepnego pojazdu - Global.changeDynObj = ( + auto *targetvehicle = ( exitdirection == end::front ? Train->DynamicObject->PrevConnected() : Train->DynamicObject->NextConnected() ); - Global.changeDynObj->MoverParameters->CabOccupied = ( + targetvehicle->MoverParameters->CabOccupied = ( Train->DynamicObject->MoverParameters->Neighbours[ exitdirection ].vehicle_end ? -1 : 1 ); + Train->MoveToVehicle( targetvehicle ); } } // HACK: match consist door permit state with the preset in the active cab @@ -5632,6 +5637,38 @@ void TTrain::OnCommand_cabchangebackward( TTrain *Train, command_data const &Com } } +void TTrain::OnCommand_vehiclemoveforwards(TTrain *Train, const command_data &Command) { + if (Command.action == GLFW_RELEASE || !DebugModeFlag) + return; + + Train->DynamicObject->move_set(100.0); +} + +void TTrain::OnCommand_vehiclemovebackwards(TTrain *Train, const command_data &Command) { + if (Command.action == GLFW_RELEASE || !DebugModeFlag) + return; + + Train->DynamicObject->move_set(-100.0); +} + +void TTrain::OnCommand_vehicleboost(TTrain *Train, const command_data &Command) { + if (Command.action == GLFW_RELEASE || !DebugModeFlag) + return; + + double boost = Command.param1 != 0.0 ? Command.param1 : 2.78; + + TDynamicObject *d = Train->DynamicObject; + while( d ) { + d->MoverParameters->V += d->DirectionGet() * boost; + d = d->Next(); // pozostałe też + } + d = Train->DynamicObject->Prev(); + while( d ) { + d->MoverParameters->V += d->DirectionGet() * boost; + d = d->Prev(); // w drugą stronę też + } +} + // cab movement update, fixed step part void TTrain::UpdateCab() { @@ -5720,7 +5757,7 @@ bool TTrain::Update( double const Deltatime ) command_data commanddata; // NOTE: currently we're only storing commands for local vehicle and there's no id system in place, // so we're supplying 'default' vehicle id of 0 - while( simulation::Commands.pop( commanddata, static_cast( command_target::vehicle ) | 0 ) ) { + while( simulation::Commands.pop( commanddata, static_cast( command_target::vehicle ) | id() ) ) { auto lookup = m_commandhandlers.find( commanddata.command ); if( lookup != m_commandhandlers.end() ) { @@ -7829,6 +7866,95 @@ void TTrain::DynamicSet(TDynamicObject *d) } }; +void +TTrain::MoveToVehicle(TDynamicObject *target) { + // > Ra: to nie może być tak robione, to zbytnia proteza jest + // indeed, too much hacks... + // TODO: cleanup + + TTrain *target_train = simulation::Trains.find(target->name()); + if (target_train) { + // let's try to destroy this TTrain and move to already existing one + + if (!Dynamic()->Mechanik || !Dynamic()->Mechanik->AIControllFlag) { + // tylko jeśli ręcznie prowadzony + // jeśli prowadzi AI, to mu nie robimy dywersji! + Occupied()->CabDeactivisation(); + Occupied()->CabOccupied = 0; + Occupied()->BrakeLevelSet(Occupied()->Handle->GetPos(bh_NP)); //rozwala sterowanie hamulcem GF 04-2016 + Dynamic()->MechInside = false; + Dynamic()->Controller = AIdriver; + + Dynamic()->bDisplayCab = false; + Dynamic()->ABuSetModelShake( {} ); + + Dynamic()->Mechanik->MoveTo(target); + + target_train->Occupied()->LimPipePress = target_train->Occupied()->PipePress; + target_train->Occupied()->CabActivisation(); // załączenie rozrządu (wirtualne kabiny) + target_train->Dynamic()->MechInside = true; + target_train->Dynamic()->Controller = Humandriver; + } else { + target_train->Dynamic()->bDisplayCab = false; + target_train->Dynamic()->ABuSetModelShake( {} ); + } + + target_train->Dynamic()->bDisplayCab = true; + target_train->Dynamic()->ABuSetModelShake( {} ); // zerowanie przesunięcia przed powrotem? + + // potentially move player + if (simulation::Train == this) { + simulation::Train = target_train; + } + + // delete this TTrain + pending_delete = true; + } else { + // move this TTrain to other dynamic + + // remove TTrain from global list, we're going to change dynamic anyway + simulation::Trains.detach(Dynamic()->name()); + + if (!Dynamic()->Mechanik || !Dynamic()->Mechanik->AIControllFlag) { + // tylko jeśli ręcznie prowadzony + // jeśli prowadzi AI, to mu nie robimy dywersji! + + Occupied()->CabDeactivisation(); + Occupied()->CabOccupied = 0; + Occupied()->BrakeLevelSet(Occupied()->Handle->GetPos(bh_NP)); //rozwala sterowanie hamulcem GF 04-2016 + Dynamic()->MechInside = false; + Dynamic()->Controller = AIdriver; + + Dynamic()->bDisplayCab = false; + Dynamic()->ABuSetModelShake( {} ); + + Dynamic()->Mechanik->MoveTo(target); + + DynamicSet(target); + + Occupied()->LimPipePress = Occupied()->PipePress; + Occupied()->CabActivisation(); // załączenie rozrządu (wirtualne kabiny) + Dynamic()->MechInside = true; + Dynamic()->Controller = Humandriver; + } else { + Dynamic()->bDisplayCab = false; + Dynamic()->ABuSetModelShake( {} ); + + DynamicSet(target); + } + + InitializeCab( + Occupied()->CabActive, + Dynamic()->asBaseDir + Occupied()->TypeName + ".mmd" ); + + Dynamic()->bDisplayCab = true; + Dynamic()->ABuSetModelShake( {} ); // zerowanie przesunięcia przed powrotem? + + // add it back with updated dynamic name + simulation::Trains.insert(this); + } +} + // checks whether specified point is within boundaries of the active cab bool TTrain::point_inside( Math3D::vector3 const Point ) const { @@ -9106,3 +9232,33 @@ bool TTrain::initialize_gauge(cParser &Parser, std::string const &Label, int con return true; } + +uint16_t TTrain::id() { + if (vid == 0) { + vid = ++simulation::prev_train_id; + WriteLog("net: assigning id " + std::to_string(vid) + " to vehicle " + Dynamic()->name(), logtype::net); + } + return vid; +} + +void train_table::update(double dt) +{ + for (TTrain *train : m_items) { + if (!train) + continue; + + train->Update(dt); + + if (train->pending_delete) { + purge(train->Dynamic()->name()); + if (simulation::Train == train) + simulation::Train = nullptr; + } + + // for single-player destroy non-player trains + if (simulation::Train != train + && Global.network_servers.empty() && !Global.network_client) { + purge(train->Dynamic()->name()); + } + } +} diff --git a/Train.h b/Train.h index 61fd6eaa..49ccfe84 100644 --- a/Train.h +++ b/Train.h @@ -130,6 +130,9 @@ class TTrain { bool LoadMMediaFile(std::string const &asFileName); dictionary_source *GetTrainState(); state_t get_state() const; + inline + std::string name() const { + return Dynamic()->name(); } private: // types @@ -384,6 +387,9 @@ class TTrain { static void OnCommand_cabchangeforward( TTrain *Train, command_data const &Command ); static void OnCommand_cabchangebackward( TTrain *Train, command_data const &Command ); static void OnCommand_generictoggle( TTrain *Train, command_data const &Command ); + static void OnCommand_vehiclemoveforwards( TTrain *Train, command_data const &Command ); + static void OnCommand_vehiclemovebackwards( TTrain *Train, command_data const &Command ); + static void OnCommand_vehicleboost( TTrain *Train, command_data const &Command ); static void OnCommand_springbraketoggle(TTrain *Train, command_data const &Command); static void OnCommand_springbrakeenable(TTrain *Train, command_data const &Command); static void OnCommand_springbrakedisable(TTrain *Train, command_data const &Command); @@ -754,6 +760,7 @@ private: bool m_mastercontrollerinuse { false }; float m_mastercontrollerreturndelay { 0.f }; std::vector> m_screens; + uint16_t vid { 0 }; // train network recipient id float m_distancecounter { -1.f }; // distance traveled since meter was activated or -1 if inactive double m_brakehandlecp{ 0.0 }; int m_pantselection{ 0 }; @@ -775,9 +782,18 @@ private: inline TMoverParameters *Occupied() { return mvOccupied; }; inline TMoverParameters const *Occupied() const { return mvOccupied; }; void DynamicSet(TDynamicObject *d); + void MoveToVehicle( TDynamicObject *target ); // checks whether specified point is within boundaries of the active cab bool point_inside( Math3D::vector3 const Point ) const; Math3D::vector3 clamp_inside( Math3D::vector3 const &Point ) const; + uint16_t id(); + bool pending_delete = false; }; + +class train_table : public basic_table { +public: + void update( double dt ); +}; + //--------------------------------------------------------------------------- diff --git a/TrkFoll.cpp b/TrkFoll.cpp index 49c48bc6..e3b5661b 100644 --- a/TrkFoll.cpp +++ b/TrkFoll.cpp @@ -38,6 +38,12 @@ bool TTrackFollower::Init(TTrack *pTrack, TDynamicObject *NewOwner, double fDir) return true; } +void TTrackFollower::Reset() +{ + fCurrentDistance = 0.0; + fDirection = 1.0; +} + TTrack * TTrackFollower::SetCurrentTrack(TTrack *pTrack, int end) { // przejechanie na inny odcinkek toru, z ewentualnym rozpruciem if (pTrack) diff --git a/TrkFoll.h b/TrkFoll.h index 05fb1b94..c2a118f5 100644 --- a/TrkFoll.h +++ b/TrkFoll.h @@ -40,6 +40,7 @@ public: //{ return pCurrentSegment->ComputeLength(p1,cp1,cp2,p2); }; // inline double GetRadius(double L, double d); //McZapkie-150503 bool Init(TTrack *pTrack, TDynamicObject *NewOwner, double fDir); + void Reset(); void Render(float fNr); // members double fOffsetH = 0.0; // Ra: odległość środka osi od osi toru (dla samochodów) - użyć do wężykowania diff --git a/application.cpp b/application.cpp index 07b94b11..71b146e1 100644 --- a/application.cpp +++ b/application.cpp @@ -43,6 +43,11 @@ http://mozilla.org/MPL/2.0/. #pragma comment (lib, "dbghelp.lib") #pragma comment (lib, "version.lib") +#ifdef __unix__ +#include +#include +#endif + extern "C" { _declspec( dllexport ) DWORD AmdPowerXpressRequestHighPerformance = 0x00000001; } extern "C" { _declspec( dllexport ) DWORD NvOptimusEnablement = 0x00000001; } @@ -65,19 +70,14 @@ extern WNDPROC BaseWindowProc; // user input callbacks void focus_callback( GLFWwindow *window, int focus ) { - if( Global.bInactivePause ) // jeśli ma być pauzowanie okna w tle - if( focus ) - Global.iPause &= ~4; // odpauzowanie, gdy jest na pierwszym planie - else - Global.iPause |= 4; // włączenie pauzy, gdy nieaktywy + Application.on_focus_change(focus != 0); } void window_resize_callback( GLFWwindow *window, int w, int h ) { // NOTE: we have two variables which basically do the same thing as we don't have dynamic fullscreen toggle // TBD, TODO: merge them? Global.iWindowWidth = w; - Global.iWindowHeight = h; - Global.fDistanceFactor = std::max( 0.5f, h / 768.0f ); // not sure if this is really something we want to use + Global.iWindowHeight = h; glViewport( 0, 0, w, h ); } @@ -101,6 +101,11 @@ void key_callback( GLFWwindow *window, int key, int scancode, int action, int mo Application.on_key( key, scancode, action, mods ); } +void char_callback(GLFWwindow *window, unsigned int c) +{ + Application.on_char(c); +} + // public: int @@ -117,10 +122,10 @@ eu07_application::init( int Argc, char *Argv[] ) { return result; } - WriteLog( "Starting MaSzyna rail vehicle simulator (release: " + Global.asVersion + ")" ); - WriteLog( "For online documentation and additional files refer to: http://eu07.pl" ); - WriteLog( "Authors: Marcin_EU, McZapkie, ABu, Winger, Tolaris, nbmx, OLO_EU, Bart, Quark-t, " - "ShaXbee, Oli_EU, youBy, KURS90, Ra, hunter, szociu, Stele, Q, firleju and others\n" ); + WriteLog( "Starting MaSzyna rail vehicle simulator (release: " + Global.asVersion + ")" ); + WriteLog( "For online documentation and additional files refer to: http://eu07.pl" ); + WriteLog( "Authors: Marcin_EU, McZapkie, ABu, Winger, Tolaris, nbmx, OLO_EU, Bart, Quark-t, " + "ShaXbee, Oli_EU, youBy, KURS90, Ra, hunter, szociu, Stele, Q, firleju and others\n" ); if( ( result = init_glfw() ) != 0 ) { return result; @@ -140,9 +145,28 @@ eu07_application::init( int Argc, char *Argv[] ) { return result; } + if (!init_network()) + return -1; + return result; } +double eu07_application::generate_sync() { + if (Timer::GetDeltaTime() == 0.0) + return 0.0; + double sync = 0.0; + for (const TDynamicObject* vehicle : simulation::Vehicles.sequence()) { + auto const pos { vehicle->GetPosition() }; + sync += pos.x + pos.y + pos.z; + } + sync += Random(1.0, 100.0); + return sync; +} + +void eu07_application::queue_quit() { + glfwSetWindowShouldClose(m_windows[0], GLFW_TRUE); +} + int eu07_application::run() { @@ -151,11 +175,125 @@ eu07_application::run() { { Timer::subsystem.mainloop_total.start(); - if( !m_modes[ m_modestack.top() ]->update() ) - break; + // ------------------------------------------------------------------- + // multiplayer command relaying logic can seem a bit complex + // + // we are simultaneously: + // => master (not client) OR slave (client) + // => server OR not + // + // trivia: being client and server is possible + + double frameStartTime = Timer::GetTime(); + + if (m_modes[m_modestack.top()]->is_command_processor()) { + // active mode is doing real calculations (e.g. drivermode) + int loop_remaining = MAX_NETWORK_PER_FRAME; + while (--loop_remaining > 0) + { + command_queue::commands_map commands_to_exec; + command_queue::commands_map local_commands = simulation::Commands.pop_intercept_queue(); + double slave_sync; + + // if we're the server + if (m_network && m_network->servers) + { + // fetch from network layer command requests received from clients + command_queue::commands_map remote_commands = m_network->servers->pop_commands(); + + // push these into local queue + add_to_dequemap(local_commands, remote_commands); + } + + // if we're slave + if (m_network && m_network->client) + { + // fetch frame info from network layer, + auto frame_info = m_network->client->get_next_delta(MAX_NETWORK_PER_FRAME - loop_remaining); + + // use delta and commands received from master + double delta = std::get<0>(frame_info); + Timer::set_delta_override(delta); + slave_sync = std::get<1>(frame_info); + add_to_dequemap(commands_to_exec, std::get<2>(frame_info)); + + // and send our local commands to master + m_network->client->send_commands(local_commands); + + if (delta == 0.0) + loop_remaining = -1; + } + // if we're master + else { + // just push local commands to execution + add_to_dequemap(commands_to_exec, local_commands); + + loop_remaining = -1; + } + + // send commands to command queue + simulation::Commands.push_commands(commands_to_exec); + + // do actual frame processing + if (!m_modes[ m_modestack.top() ]->update()) + return 0; + + double sync = generate_sync(); + + // if we're the server + if (m_network && m_network->servers) + { + // send delta, sync, and commands we just executed to clients + double delta = Timer::GetDeltaTime(); + double render = Timer::GetDeltaRenderTime(); + m_network->servers->push_delta(render, delta, sync, commands_to_exec); + } + + // if we're slave + if (m_network && m_network->client) + { + // verify sync + if (sync != slave_sync) { + WriteLog("net: desync! calculated: " + std::to_string(sync) + + ", received: " + std::to_string(slave_sync), logtype::net); + + Global.desync = slave_sync - sync; + } + + // set total delta for rendering code + double totalDelta = Timer::GetTime() - frameStartTime; + Timer::set_delta_override(totalDelta); + } + } + + if (!loop_remaining) { + // loop break forced by counter + float received = m_network->client->get_frame_counter(); + float awaiting = m_network->client->get_awaiting_frames(); + + // TODO: don't meddle with mode progresbar + m_modes[m_modestack.top()]->set_progress(100.0f, 100.0f * (received - awaiting) / received); + } else { + m_modes[m_modestack.top()]->set_progress(0.0f, 0.0f); + } + } else { + // active mode is loader + + // clear local command queue + simulation::Commands.pop_intercept_queue(); + + // do actual frame processing + if (!m_modes[ m_modestack.top() ]->update()) + return 0; + } + + // ------------------------------------------------------------------- + + // TODO: re-enable after python changes merge + // m_taskqueue.update(); if (!GfxRenderer->Render()) - break; + return 0; glfwPollEvents(); @@ -164,10 +302,15 @@ eu07_application::run() { m_modes[ m_modestack.top() ]->on_event_poll(); - Timer::subsystem.mainloop_total.stop(); + if (m_network) + m_network->update(); + + auto frametime = Timer::subsystem.mainloop_total.stop(); + if (Global.minframetime.count() != 0.0f && (Global.minframetime - frametime).count() > 0.0f) + std::this_thread::sleep_for(Global.minframetime - frametime); } - return 0; + return 0; } // issues request for a worker thread to perform specified task. returns: true if task was scheduled @@ -199,8 +342,12 @@ eu07_application::release_python_lock() { void eu07_application::exit() { + for (auto &mode : m_modes) + mode.reset(); - SafeDelete( simulation::Train ); + m_network.reset(); + +// SafeDelete( simulation::Train ); SafeDelete( simulation::Region ); ui_layer::shutdown(); @@ -208,8 +355,11 @@ eu07_application::exit() { for( auto *window : m_windows ) { glfwDestroyWindow( window ); } - glfwTerminate(); m_taskqueue.exit(); + glfwTerminate(); + + if (!Global.exec_on_exit.empty()) + system(Global.exec_on_exit.c_str()); } void @@ -222,7 +372,6 @@ eu07_application::render_ui() { bool eu07_application::pop_mode() { - if( m_modestack.empty() ) { return false; } m_modes[ m_modestack.top() ]->exit(); @@ -291,6 +440,7 @@ eu07_application::get_cursor_pos( double &Horizontal, double &Vertical ) const { glfwGetCursorPos( m_windows.front(), &Horizontal, &Vertical ); } + int eu07_application::get_mouse_button( int const Button ) const { @@ -314,6 +464,13 @@ eu07_application::on_key( int const Key, int const Scancode, int const Action, i m_modes[ m_modestack.top() ]->on_key( Key, Scancode, Action, Mods ); } +void eu07_application::on_char( unsigned int const Char ) { + + if( m_modestack.empty() ) { return; } + + m_modes[ m_modestack.top() ]->on_char( Char ); +} + void eu07_application::on_cursor_pos( double const Horizontal, double const Vertical ) { @@ -338,6 +495,13 @@ eu07_application::on_scroll( double const Xoffset, double const Yoffset ) { m_modes[ m_modestack.top() ]->on_scroll( Xoffset, Yoffset ); } +void eu07_application::on_focus_change(bool focus) { + if( Global.bInactivePause && !m_network->client) {// jeśli ma być pauzowanie okna w tle + command_relay relay; + relay.post(user_command::focuspauseset, focus ? 1.0 : 0.0, 0.0, GLFW_PRESS, 0); + } +} + GLFWwindow * eu07_application::window(int const Windowindex, bool visible, int width, int height, GLFWmonitor *monitor, bool keep_ownership , bool share_ctx) { @@ -373,7 +537,7 @@ eu07_application::window(int const Windowindex, bool visible, int width, int hei // private: GLFWmonitor* eu07_application::find_monitor(const std::string &str) const { - int monitor_count; + int monitor_count; GLFWmonitor **monitors = glfwGetMonitors(&monitor_count); for (size_t i = 0; i < monitor_count; i++) { @@ -394,17 +558,21 @@ std::string eu07_application::describe_monitor(GLFWmonitor *monitor) const { return name + ":" + std::to_string(x) + "," + std::to_string(y); } +// private: + void eu07_application::init_debug() { #if defined(_MSC_VER) && defined (_DEBUG) // memory leaks _CrtSetDbgFlag( _CrtSetDbgFlag( _CRTDBG_REPORT_FLAG ) | _CRTDBG_LEAK_CHECK_DF ); + /* // floating point operation errors auto state { _clearfp() }; state = _control87( 0, 0 ); // this will turn on FPE for #IND and zerodiv state = _control87( state & ~( _EM_ZERODIVIDE | _EM_INVALID ), _MCW_EM ); + */ #endif #ifdef _WIN32 ::SetUnhandledExceptionFilter( unhandled_handler ); @@ -417,7 +585,11 @@ eu07_application::init_files() { #ifdef _WIN32 DeleteFile( "log.txt" ); DeleteFile( "errors.txt" ); - _mkdir( "logs" ); + CreateDirectory( "logs", NULL ); +#elif __unix__ + unlink("log.txt"); + unlink("errors.txt"); + mkdir("logs", 0755); #endif } @@ -425,10 +597,12 @@ int eu07_application::init_settings( int Argc, char *Argv[] ) { Global.LoadIniFile( "eu07.ini" ); +#ifdef _WIN32 if( ( Global.iWriteLogEnabled & 2 ) != 0 ) { // show output console if requested AllocConsole(); } +#endif /* std::string executable( argv[ 0 ] ); auto const pathend = executable.rfind( '\\' ); Global.ExecutableName = @@ -486,7 +660,7 @@ eu07_application::init_settings( int Argc, char *Argv[] ) { } else if( token == "-v" ) { if( i + 1 < Argc ) { - Global.asHumanCtrlVehicle = ToLower( Argv[ ++i ] ); + Global.local_start_vehicle = ToLower( Argv[ ++i ] ); } } else { @@ -588,6 +762,7 @@ eu07_application::init_callbacks() { glfwSetMouseButtonCallback( window, mouse_button_callback ); glfwSetKeyCallback( window, key_callback ); glfwSetScrollCallback( window, scroll_callback ); + glfwSetCharCallback(window, char_callback); glfwSetWindowFocusCallback( window, focus_callback ); { int width, height; @@ -668,7 +843,12 @@ eu07_application::init_data() { int eu07_application::init_modes() { + Global.local_random_engine.seed(std::random_device{}()); + if ((!Global.network_servers.empty() || Global.network_client) && Global.SceneryFile.empty()) { + ErrorLog("launcher mode is currently not supported in network mode"); + return -1; + } // NOTE: we could delay creation/initialization until transition to specific mode is requested, // but doing it in one go at the start saves us some error checking headache down the road @@ -687,3 +867,42 @@ eu07_application::init_modes() { return 0; } + +bool eu07_application::init_network() { + if (!Global.network_servers.empty() || Global.network_client) { + // create network manager + m_network.emplace(); + } + + for (auto const &pair : Global.network_servers) { + // create all servers + m_network->create_server(pair.first, pair.second); + } + + if (Global.network_client) { + // create client + m_network->connect(Global.network_client->first, Global.network_client->second); + } else { + // we're simulation master + if (!Global.random_seed) + Global.random_seed = std::random_device{}(); + Global.random_engine.seed(Global.random_seed); + + // TODO: sort out this timezone mess + std::time_t utc_now = std::time(nullptr); + + tm tm_local, tm_utc; + tm *tmp = std::localtime(&utc_now); + memcpy(&tm_local, tmp, sizeof(tm)); + tmp = std::gmtime(&utc_now); + memcpy(&tm_utc, tmp, sizeof(tm)); + + int64_t offset = (tm_local.tm_hour * 3600 + tm_local.tm_min * 60 + tm_local.tm_sec) + - (tm_utc.tm_hour * 3600 + tm_utc.tm_min * 60 + tm_utc.tm_sec); + + Global.starting_timestamp = utc_now + offset; + Global.ready_to_load = true; + } + + return true; +} diff --git a/application.h b/application.h index 8f987556..f2006c07 100644 --- a/application.h +++ b/application.h @@ -11,8 +11,10 @@ http://mozilla.org/MPL/2.0/. #include "applicationmode.h" #include "PyInt.h" +#include "network/manager.h" class eu07_application { + const int MAX_NETWORK_PER_FRAME = 1000; public: // types @@ -72,15 +74,24 @@ public: // input handlers void on_key( int const Key, int const Scancode, int const Action, int const Mods ); + void + on_char( unsigned int const Char ); void on_cursor_pos( double const Horizontal, double const Vertical ); void on_mouse_button( int const Button, int const Action, int const Mods ); void on_scroll( double const Xoffset, double const Yoffset ); + void + on_focus_change(bool focus); // gives access to specified window, creates a new window if index == -1 GLFWwindow * window( int const Windowindex = 0, bool visible = false, int width = 1, int height = 1, GLFWmonitor *monitor = nullptr, bool keep_ownership = true, bool share_ctx = true ); + // generate network sync verification number + double + generate_sync(); + void + queue_quit(); private: // types @@ -97,6 +108,7 @@ private: int init_audio(); int init_data(); int init_modes(); + bool init_network(); GLFWmonitor * find_monitor( const std::string &str ) const; std::string describe_monitor( GLFWmonitor *monitor ) const; // members @@ -104,6 +116,8 @@ private: mode_stack m_modestack; // current behaviour mode python_taskqueue m_taskqueue; std::vector m_windows; + + std::optional m_network; }; extern eu07_application Application; diff --git a/applicationmode.h b/applicationmode.h index f3f925c3..30053e84 100644 --- a/applicationmode.h +++ b/applicationmode.h @@ -57,6 +57,9 @@ public: void on_key( int const Key, int const Scancode, int const Action, int const Mods ) = 0; virtual + void + on_char( unsigned int const Char ) = 0; + virtual void on_cursor_pos( double const X, double const Y ) = 0; virtual @@ -68,6 +71,9 @@ public: virtual void on_event_poll() = 0; + virtual + bool + is_command_processor() = 0; protected: // members diff --git a/command.cpp b/command.cpp index fb91f1d9..d40a1018 100644 --- a/command.cpp +++ b/command.cpp @@ -14,6 +14,8 @@ http://mozilla.org/MPL/2.0/. #include "Logs.h" #include "Timer.h" #include "utilities.h" +#include "simulation.h" +#include "Train.h" namespace simulation { @@ -266,7 +268,36 @@ commanddescription_sequence Commands_descriptions = { { "speedcontrolbutton6", command_target::vehicle }, { "speedcontrolbutton7", command_target::vehicle }, { "speedcontrolbutton8", command_target::vehicle }, - { "speedcontrolbutton9", command_target::vehicle } + { "speedcontrolbutton9", command_target::vehicle }, + + { "globalradiostop", command_target::simulation }, + { "timejump", command_target::simulation }, + { "timejumplarge", command_target::simulation }, + { "timejumpsmall", command_target::simulation }, + { "setdatetime", command_target::simulation }, + { "setweather", command_target::simulation }, + { "settemperature", command_target::simulation }, + { "vehiclemoveforwards", command_target::vehicle }, + { "vehiclemovebackwards", command_target::vehicle }, + { "vehicleboost", command_target::vehicle }, + { "debugtoggle", command_target::simulation }, + { "focuspauseset", command_target::simulation }, + { "pausetoggle", command_target::simulation }, + { "entervehicle", command_target::simulation }, + { "resetconsist", command_target::simulation }, + { "fillcompressor", command_target::simulation }, + { "consistreleaser", command_target::simulation }, + { "queueevent", command_target::simulation }, + { "setlight", command_target::simulation }, + { "insertmodel", command_target::simulation }, + { "deletemodel", command_target::simulation }, + { "trainsetmove", command_target::simulation }, + { "consistteleport", command_target::simulation }, + { "pullalarmchain", command_target::simulation }, + { "sendaicommand", command_target::simulation }, + { "spawntrainset", command_target::simulation }, + { "destroytrainset", command_target::simulation }, + { "quitsimulation", command_target::simulation }, }; } // simulation @@ -274,10 +305,18 @@ commanddescription_sequence Commands_descriptions = { // posts specified command for specified recipient void command_queue::push( command_data const &Command, std::size_t const Recipient ) { + if (is_network_target(Recipient)) { + auto lookup = m_intercept_queue.emplace(Recipient, commanddata_sequence()); + lookup.first->second.emplace_back(Command); + } else { + push_direct(Command, Recipient); + } +} - auto lookup = m_commands.emplace( Recipient, commanddata_sequence() ); - // recipient stack was either located or created, so we can add to it quite safely - lookup.first->second.emplace( Command ); +void command_queue::push_direct(const command_data &Command, const uint32_t Recipient) { + auto lookup = m_commands.emplace( Recipient, commanddata_sequence() ); + // recipient stack was either located or created, so we can add to it quite safely + lookup.first->second.emplace_back( Command ); } // retrieves oldest posted command for specified recipient, if any. returns: true on retrieval, false if there's nothing to retrieve @@ -296,15 +335,45 @@ command_queue::pop( command_data &Command, std::size_t const Recipient ) { } // we have command stack with command(s) on it, retrieve and pop the first one Command = commands.front(); - commands.pop(); + commands.pop_front(); return true; } +bool command_queue::is_network_target(uint32_t const Recipient) { + const command_target target = (command_target)(Recipient & ~0xffff); + + if (target == command_target::entity) + return false; + + return true; +} + +command_queue::commands_map command_queue::pop_intercept_queue() { + commands_map map(m_intercept_queue); + m_intercept_queue.clear(); + return map; +} + +void command_queue::push_commands(const commands_map &commands) { + for (auto const &kv : commands) + for (command_data const &data : kv.second) + push_direct(data, kv.first); +} + void -command_relay::post( user_command const Command, double const Param1, double const Param2, int const Action, std::uint16_t const Recipient ) const { +command_relay::post(user_command const Command, double const Param1, double const Param2, + int const Action, uint16_t Recipient, glm::vec3 Position, const std::string *Payload) const { auto const &command = simulation::Commands_descriptions[ static_cast( Command ) ]; + + if (command.target == command_target::vehicle && Recipient == 0) { + // default 0 recipient is currently controlled train + if (simulation::Train == nullptr) + return; + Recipient = simulation::Train->id(); + } + if( ( command.target == command_target::vehicle ) && ( true == FreeFlyModeFlag ) && ( ( false == DebugModeFlag ) @@ -313,44 +382,15 @@ command_relay::post( user_command const Command, double const Param1, double con return; } - simulation::Commands.push( - command_data{ - Command, - Action, - Param1, - Param2, - Timer::GetDeltaTime() }, - static_cast( command.target ) | Recipient ); -/* -#ifdef _DEBUG - if( Action != GLFW_RELEASE ) { - // key was pressed or is still held - if( false == command.name.empty() ) { - if( false == ( - ( Command == user_command::moveleft ) - || ( Command == user_command::moveleftfast ) - || ( Command == user_command::moveright ) - || ( Command == user_command::moverightfast ) - || ( Command == user_command::moveforward ) - || ( Command == user_command::moveforwardfast ) - || ( Command == user_command::moveback ) - || ( Command == user_command::movebackfast ) - || ( Command == user_command::moveup ) - || ( Command == user_command::moveupfast ) - || ( Command == user_command::movedown ) - || ( Command == user_command::movedownfast ) - || ( Command == user_command::movevector ) - || ( Command == user_command::viewturn ) ) ) { - WriteLog( "Command issued: " + command.name ); - } - } - } - else { - // key was released (but we don't log this) - WriteLog( "Key released: " + command.name ); - } -#endif -*/ + if (Position == glm::vec3(0.0f)) + Position = Global.pCamera.Pos; + + uint32_t combined_recipient = static_cast( command.target ) | Recipient; + command_data commanddata({Command, Action, Param1, Param2, Timer::GetDeltaTime(), FreeFlyModeFlag, Position }); + if (Payload) + commanddata.payload = *Payload; + + simulation::Commands.push(commanddata, combined_recipient); } //--------------------------------------------------------------------------- diff --git a/command.h b/command.h index 18960093..215bd3ad 100644 --- a/command.h +++ b/command.h @@ -260,13 +260,42 @@ enum class user_command { speedcontrolbutton7, speedcontrolbutton8, speedcontrolbutton9, + + globalradiostop, + timejump, + timejumplarge, + timejumpsmall, + setdatetime, + setweather, + settemperature, + vehiclemoveforwards, + vehiclemovebackwards, + vehicleboost, + debugtoggle, + focuspauseset, + pausetoggle, + entervehicle, + resetconsist, + fillcompressor, + consistreleaser, + queueevent, + setlight, + insertmodel, + deletemodel, + dynamicmove, + consistteleport, + pullalarmchain, + sendaicommand, + spawntrainset, + destroytrainset, + quitsimulation, + none = -1 }; enum class command_target { userinterface, - simulation, /* // NOTE: there's no need for consist- and unit-specific commands at this point, but it's a possibility. // since command targets are mutually exclusive these don't reduce ranges for individual vehicles etc @@ -276,7 +305,9 @@ enum class command_target { // values are combined with object id. 0xffff objects of each type should be quite enough ("for everyone") vehicle = 0x10000, signal = 0x20000, - entity = 0x40000 + entity = 0x40000, + + simulation = 0x80000 }; struct command_description { @@ -292,6 +323,11 @@ struct command_data { double param1; double param2; double time_delta; + + bool freefly; + glm::vec3 location; + + std::string payload; }; // command_queues: collects and holds commands from input sources, for processing by their intended recipients @@ -301,6 +337,9 @@ struct command_data { class command_queue { public: +// types + typedef std::deque commanddata_sequence; + typedef std::unordered_map commands_map; // methods // posts specified command for specified recipient void @@ -308,16 +347,36 @@ public: // retrieves oldest posted command for specified recipient, if any. returns: true on retrieval, false if there's nothing to retrieve bool pop( command_data &Command, std::size_t const Recipient ); + // checks if given command must be scheduled on server + bool + is_network_target(const uint32_t Recipient); + + // pops commands from intercept queue + commands_map pop_intercept_queue(); + + // pushes commands into main queue + void push_commands(const commands_map &commands); private: -// types - typedef std::queue commanddata_sequence; - typedef std::unordered_map commanddatasequence_map; // members - commanddatasequence_map m_commands; + // contains command ready to execution + commands_map m_commands; + // contains intercepted commands to be read by application layer + commands_map m_intercept_queue; + + void push_direct( command_data const &Command, uint32_t const Recipient ); }; +template +void add_to_dequemap(std::unordered_map> &lhs, const std::unordered_map> &rhs) { + for (auto const &kv : rhs) { + auto lookup = lhs.emplace(kv.first, std::deque()); + for (B const &data : kv.second) + lookup.first->second.emplace_back(data); + } +} + // NOTE: simulation should be a (light) wrapper rather than namespace so we could potentially instance it, // but realistically it's not like we're going to run more than one simulation at a time namespace simulation { @@ -340,8 +399,8 @@ public: // posts specified command for the specified recipient // TODO: replace uint16_t with recipient handle, based on item id void - post( user_command const Command, double const Param1, double const Param2, - int const Action, std::uint16_t const Recipient ) const; + post(user_command const Command, double const Param1, double const Param2, + int const Action, uint16_t Recipient, glm::vec3 Position = glm::vec3(0.0f) , const std::string *Payload = nullptr) const; private: // types // members diff --git a/driverkeyboardinput.cpp b/driverkeyboardinput.cpp index fdb1aba0..cf461660 100644 --- a/driverkeyboardinput.cpp +++ b/driverkeyboardinput.cpp @@ -244,7 +244,7 @@ driverkeyboard_input::default_bindings() { // batterydisable, { user_command::motorblowerstogglefront, GLFW_KEY_N | keymodifier::shift }, { user_command::motorblowerstogglerear, GLFW_KEY_M | keymodifier::shift }, - { user_command::motorblowersdisableall, GLFW_KEY_M | keymodifier::control } + { user_command::motorblowersdisableall, GLFW_KEY_M | keymodifier::control }, // coolingfanstoggle, // tempomattoggle, // springbraketoggle, @@ -269,6 +269,15 @@ driverkeyboard_input::default_bindings() { // speedcontrolbutton7, // speedcontrolbutton8, // speedcontrolbutton9, + // admin_timejump, + { user_command::timejumplarge, GLFW_KEY_F1 | keymodifier::control }, + { user_command::timejumpsmall, GLFW_KEY_F1 | keymodifier::shift }, + // admin_vehiclemove, + { user_command::vehiclemoveforwards, GLFW_KEY_LEFT_BRACKET | keymodifier::control }, + { user_command::vehiclemovebackwards, GLFW_KEY_RIGHT_BRACKET | keymodifier::control }, + { user_command::vehicleboost, GLFW_KEY_TAB | keymodifier::control }, + { user_command::debugtoggle, GLFW_KEY_F12 | keymodifier::control | keymodifier::shift }, + { user_command::pausetoggle, GLFW_KEY_ESCAPE } }; } diff --git a/drivermode.cpp b/drivermode.cpp index 86771561..007f5379 100644 --- a/drivermode.cpp +++ b/drivermode.cpp @@ -226,11 +226,26 @@ driver_mode::update() { // variable step simulation time routines + if (!change_train.empty()) { + TTrain *train = simulation::Trains.find(change_train); + if (train) { + simulation::Train = train; + InOutKey(); + m_relay.post(user_command::aidriverdisable, 0.0, 0.0, GLFW_PRESS, 0); + change_train.clear(); + } + } + if( ( simulation::Train == nullptr ) && ( false == FreeFlyModeFlag ) ) { // intercept cases when the driven train got removed after entering portal InOutKey(); } + if (!FreeFlyModeFlag && simulation::Train->Dynamic() != Camera.m_owner) { + // fixup camera after vehicle switch + CabView(); + } +/* if( Global.changeDynObj ) { // ABu zmiana pojazdu - przejście do innego ChangeDynamic(); @@ -243,15 +258,13 @@ driver_mode::update() { InOutKey(); } } +*/ + if( simulation::Train != nullptr ) + TSubModel::iInstance = reinterpret_cast( simulation::Train->Dynamic() ); + else + TSubModel::iInstance = 0; - if( simulation::Train != nullptr ) { - TSubModel::iInstance = reinterpret_cast( simulation::Train->Dynamic() ); - simulation::Train->Update( deltatime ); - } - else { - TSubModel::iInstance = 0; - } - + simulation::Trains.update(deltatime); simulation::Events.update(); simulation::Region->update_events(); simulation::Lights.update(); @@ -260,12 +273,16 @@ driver_mode::update() { auto const deltarealtime = Timer::GetDeltaRenderTime(); // nie uwzględnia pauzowania ani mnożenia czasu + simulation::State.process_commands(); + // fixed step render time routines fTime50Hz += deltarealtime; // w pauzie też trzeba zliczać czas, bo przy dużym FPS będzie problem z odczytem ramek bool runonce { false }; while( fTime50Hz >= 1.0 / 50.0 ) { +#ifdef _WIN32 Console::Update(); // to i tak trzeba wywoływać +#endif ui::Transcripts.Update(); // obiekt obsługujący stenogramy dźwięków na ekranie m_userinterface->update(); // decelerate camera @@ -354,8 +371,8 @@ void driver_mode::enter() { TDynamicObject *nPlayerTrain { ( - ( Global.asHumanCtrlVehicle != "ghostview" ) ? - simulation::Vehicles.find( Global.asHumanCtrlVehicle ) : + ( Global.local_start_vehicle != "ghostview" ) ? + simulation::Vehicles.find( Global.local_start_vehicle ) : nullptr ) }; Camera.Init(Global.FreeCameraInit[0], Global.FreeCameraInitAngle[0], nPlayerTrain ); @@ -364,32 +381,17 @@ driver_mode::enter() { if (nPlayerTrain) { - WriteLog( "Initializing player train, \"" + Global.asHumanCtrlVehicle + "\"" ); + WriteLog( "Trying to enter player train, \"" + Global.local_start_vehicle + "\"" ); - if( simulation::Train == nullptr ) { - simulation::Train = new TTrain(); - } - if( simulation::Train->Init( nPlayerTrain ) ) - { - WriteLog("Player train initialization OK"); - - Application.set_title( Global.AppName + " (" + simulation::Train->Controlled()->Name + " @ " + Global.SceneryFile + ")" ); - - CabView(); - } - else - { - Error("Bad init: player train initialization failed"); - FreeFlyModeFlag = true; // Ra: automatycznie włączone latanie - SafeDelete( simulation::Train ); - Camera.m_owner = nullptr; - } + FreeFlyModeFlag = true; // HACK: entervehicle won't work if the simulation thinks we're outside + m_relay.post(user_command::entervehicle, 0.0, 0.0, GLFW_PRESS, 0, nPlayerTrain->GetPosition()); + change_train = nPlayerTrain->name(); } else { - if (Global.asHumanCtrlVehicle != "ghostview") + if (Global.local_start_vehicle != "ghostview") { - Error("Bad scenario: failed to locate player train, \"" + Global.asHumanCtrlVehicle + "\"" ); + Error("Bad scenario: failed to locate player train, \"" + Global.local_start_vehicle + "\"" ); } FreeFlyModeFlag = true; // Ra: automatycznie włączone latanie Camera.m_owner = nullptr; @@ -459,6 +461,11 @@ driver_mode::on_key( int const Key, int const Scancode, int const Action, int co } } +void +driver_mode::on_char( unsigned int const Char ) { + // TODO: implement +} + void driver_mode::on_cursor_pos( double const Horizontal, double const Vertical ) { @@ -498,6 +505,12 @@ driver_mode::on_event_poll() { m_input.poll(); } +bool +driver_mode::is_command_processor() { + + return true; +} + void driver_mode::update_camera( double const Deltatime ) { @@ -759,7 +772,7 @@ driver_mode::OnKeyDown(int cKey) { // z [Shift] uruchomienie eventu if( ( Global.iPause == 0 ) // podczas pauzy klawisze nie działają && ( KeyEvents[ i ] != nullptr ) ) { - simulation::Events.AddToQuery( KeyEvents[ i ], NULL ); + m_relay.post(user_command::queueevent, 0.0, 0.0, GLFW_PRESS, 0, glm::vec3(0.0f), &KeyEvents[i]->name()); } } else if( Global.ctrlState ) { @@ -794,21 +807,6 @@ driver_mode::OnKeyDown(int cKey) { switch (cKey) { - case GLFW_KEY_F1: { - - if( DebugModeFlag ) { - // additional simulation clock jump keys in debug mode - if( Global.ctrlState ) { - // ctrl-f1 - simulation::Time.update( 20.0 * 60.0 ); - } - else if( Global.shiftState ) { - // shift-f1 - simulation::Time.update( 5.0 * 60.0 ); - } - } - break; - } case GLFW_KEY_F4: { if( Global.shiftState ) { ExternalView(); } // with Shift, cycle through external views @@ -817,6 +815,19 @@ driver_mode::OnKeyDown(int cKey) { } case GLFW_KEY_F5: { // przesiadka do innego pojazdu + if (!FreeFlyModeFlag) + // only available in free fly mode + break; + + TDynamicObject *dynamic = std::get( simulation::Region->find_vehicle( Global.pCamera.Pos, 50, false, false ) ); + if (dynamic) { + m_relay.post(user_command::entervehicle, 0.0, 0.0, GLFW_PRESS, 0); + + change_train = dynamic->name(); + } + + break; +/* if( false == FreeFlyModeFlag ) { // only available in free fly mode break; @@ -872,12 +883,10 @@ driver_mode::OnKeyDown(int cKey) { // we can simply move the 'human' controller to the new vehicle Global.changeDynObj = targetvehicle; // TODO: choose active cab based on camera's location relative to vehicle's location -/* - Global.changeDynObj->MoverParameters->CabOccupied = ( - Train->DynamicObject->MoverParameters->Neighbours[ exitdirection ].vehicle_end ? - -1 : - 1 ); -*/ +// Global.changeDynObj->MoverParameters->CabOccupied = ( +// Train->DynamicObject->MoverParameters->Neighbours[ exitdirection ].vehicle_end ? +// -1 : +// 1 ); } } } @@ -905,6 +914,7 @@ driver_mode::OnKeyDown(int cKey) { } } break; +*/ } case GLFW_KEY_F6: { // przyspieszenie symulacji do testowania scenerii... uwaga na FPS! @@ -954,66 +964,6 @@ driver_mode::OnKeyDown(int cKey) { } break; } - case GLFW_KEY_F12: { - // quick debug mode toggle - if( Global.ctrlState - && Global.shiftState ) { - DebugModeFlag = !DebugModeFlag; - } - break; - } - - case GLFW_KEY_LEFT_BRACKET: - case GLFW_KEY_RIGHT_BRACKET: - case GLFW_KEY_TAB: { - // consist movement in debug mode - if( ( true == DebugModeFlag ) - && ( false == Global.shiftState ) - && ( true == Global.ctrlState ) - && ( simulation::Train != nullptr ) - && ( simulation::Train->Dynamic()->Controller == Humandriver ) ) { - - if( DebugModeFlag ) { - // przesuwanie składu o 100m - auto *vehicle { simulation::Train->Dynamic() }; - TDynamicObject *d = vehicle; - if( cKey == GLFW_KEY_LEFT_BRACKET ) { - while( d ) { - d->Move( 100.0 * d->DirectionGet() ); - d = d->Next(); // pozostałe też - } - d = vehicle->Prev(); - while( d ) { - d->Move( 100.0 * d->DirectionGet() ); - d = d->Prev(); // w drugą stronę też - } - } - else if( cKey == GLFW_KEY_RIGHT_BRACKET ) { - while( d ) { - d->Move( -100.0 * d->DirectionGet() ); - d = d->Next(); // pozostałe też - } - d = vehicle->Prev(); - while( d ) { - d->Move( -100.0 * d->DirectionGet() ); - d = d->Prev(); // w drugą stronę też - } - } - else if( cKey == GLFW_KEY_TAB ) { - while( d ) { - d->MoverParameters->V += d->DirectionGet()*2.78; - d = d->Next(); // pozostałe też - } - d = vehicle->Prev(); - while( d ) { - d->MoverParameters->V += d->DirectionGet()*2.78; - d = d->Prev(); // w drugą stronę też - } - } - } - } - break; - } default: { break; @@ -1267,7 +1217,7 @@ driver_mode::ChangeDynamic() { vehicle = train->Dynamic(); occupied = train->Occupied(); driver = vehicle->Mechanik; - Global.asHumanCtrlVehicle = vehicle->name(); + Global.local_start_vehicle = vehicle->name(); if( driver ) // AI może sobie samo pójść if( false == driver->AIControllFlag ) // tylko jeśli ręcznie prowadzony { diff --git a/drivermode.h b/drivermode.h index 61d1606e..eedd307b 100644 --- a/drivermode.h +++ b/drivermode.h @@ -41,6 +41,8 @@ public: // input handlers void on_key( int const Key, int const Scancode, int const Action, int const Mods ) override; + void + on_char( unsigned int const Char ) override; void on_cursor_pos( double const Horizontal, double const Vertical ) override; void @@ -49,6 +51,8 @@ public: on_scroll( double const Xoffset, double const Yoffset ) override; void on_event_poll() override; + bool + is_command_processor() override; private: // types @@ -109,4 +113,6 @@ private: double m_primaryupdateaccumulator { m_secondaryupdaterate }; // keeps track of elapsed simulation time, for core fixed step routines double m_secondaryupdateaccumulator { m_secondaryupdaterate }; // keeps track of elapsed simulation time, for less important fixed step routines int iPause { 0 }; // wykrywanie zmian w zapauzowaniu + command_relay m_relay; + std::string change_train; // train name awaiting entering }; diff --git a/driveruilayer.cpp b/driveruilayer.cpp index 1ac6acbe..edf28499 100644 --- a/driveruilayer.cpp +++ b/driveruilayer.cpp @@ -47,22 +47,6 @@ driver_ui::driver_ui() { bool driver_ui::on_key_( int const Key, int const Scancode, int const Action, int const Mods ) { - if( Key == GLFW_KEY_ESCAPE ) { - // toggle pause - if( Action != GLFW_PRESS ) { return true; } // recognized, but ignored - - if( Global.iPause & 1 ) { - // jeśli pauza startowa - // odpauzowanie, gdy po wczytaniu miało nie startować - Global.iPause ^= 1; - } - else if( ( Global.iMultiplayer & 2 ) == 0 ) { - // w multiplayerze pauza nie ma sensu - Global.iPause ^= 2; // zmiana stanu zapauzowania - } - return true; - } - // if the pause is on ignore block other input if( m_paused ) { return true; } @@ -192,12 +176,13 @@ driver_ui::render_() { auto const popupwidth{ locale::strings[ locale::string::driver_pause_header ].size() * 7 }; if( ImGui::Button( locale::strings[ locale::string::driver_pause_resume ].c_str(), ImVec2( popupwidth, 0 ) ) ) { ImGui::CloseCurrentPopup(); - auto const pausemask { 1 | 2 }; - Global.iPause &= ~pausemask; + command_relay commandrelay; + commandrelay.post(user_command::pausetoggle, 0.0, 0.0, GLFW_PRESS, 0); } if( ImGui::Button( locale::strings[ locale::string::driver_pause_quit ].c_str(), ImVec2( popupwidth, 0 ) ) ) { ImGui::CloseCurrentPopup(); - glfwSetWindowShouldClose( m_window, 1 ); + command_relay commandrelay; + commandrelay.post(user_command::quitsimulation, 0.0, 0.0, GLFW_PRESS, 0); } ImGui::EndPopup(); } diff --git a/editormode.cpp b/editormode.cpp index 340fe2ff..9a725dc5 100644 --- a/editormode.cpp +++ b/editormode.cpp @@ -181,6 +181,11 @@ editor_mode::on_key( int const Key, int const Scancode, int const Action, int co } } +void +editor_mode::on_char( unsigned int const Char ) { + // TODO: implement +} + void editor_mode::on_cursor_pos( double const Horizontal, double const Vertical ) { @@ -266,6 +271,12 @@ editor_mode::on_event_poll() { m_input.poll(); } +bool +editor_mode::is_command_processor() { + + return false; +} + bool editor_mode::mode_translation() const { diff --git a/editormode.h b/editormode.h index df146561..52e990ef 100644 --- a/editormode.h +++ b/editormode.h @@ -37,6 +37,8 @@ public: // input handlers void on_key( int const Key, int const Scancode, int const Action, int const Mods ) override; + void + on_char( unsigned int const Char ) override; void on_cursor_pos( double const Horizontal, double const Vertical ) override; void @@ -45,6 +47,8 @@ public: on_scroll( double const Xoffset, double const Yoffset ) override; void on_event_poll() override; + bool + is_command_processor() override; private: // types diff --git a/maszyna.vcxproj b/maszyna.vcxproj index 30a5a291..57ce694a 100644 --- a/maszyna.vcxproj +++ b/maszyna.vcxproj @@ -84,10 +84,10 @@ - WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) + WIN32;WIN32_LEAN_AND_MEAN;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) Level3 ProgramDatabase - $(SolutionDir);$(SolutionDir)console;$(SolutionDir)mczapkie;$(SolutionDir)ref/glad/include;$(SolutionDir)ref/glfw/include;$(SolutionDir)ref/glm;$(SolutionDir)ref/openal/include;$(SolutionDir)ref/dr_libs/include;$(SolutionDir)ref/imgui;$(SolutionDir)ref/imgui/examples;$(SolutionDir)ref/python/include;$(SolutionDir)ref/libserialport/include;$(SolutionDir)ref/stb;%(AdditionalIncludeDirectories) + $(SolutionDir);$(SolutionDir)console;$(SolutionDir)mczapkie;$(SolutionDir)ref/glad/include;$(SolutionDir)ref/glfw/include;$(SolutionDir)ref/glm;$(SolutionDir)ref/openal/include;$(SolutionDir)ref/dr_libs/include;$(SolutionDir)ref/imgui;$(SolutionDir)ref/imgui/examples;$(SolutionDir)ref/python/include;$(SolutionDir)ref/libserialport/include;$(SolutionDir)ref/asio/include;$(SolutionDir)ref/stb;%(AdditionalIncludeDirectories) Use true false @@ -108,10 +108,10 @@ - WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) + WIN32;WIN32_LEAN_AND_MEAN;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) Level3 ProgramDatabase - $(SolutionDir);$(SolutionDir)console;$(SolutionDir)mczapkie;$(SolutionDir)ref/glad/include;$(SolutionDir)ref/glfw/include;$(SolutionDir)ref/glm;$(SolutionDir)ref/openal/include;$(SolutionDir)ref/dr_libs/include;$(SolutionDir)ref/imgui;$(SolutionDir)ref/imgui/examples;$(SolutionDir)ref/python/include;$(SolutionDir)ref/libserialport/include;$(SolutionDir)ref/stb;%(AdditionalIncludeDirectories) + $(SolutionDir);$(SolutionDir)console;$(SolutionDir)mczapkie;$(SolutionDir)ref/glad/include;$(SolutionDir)ref/glfw/include;$(SolutionDir)ref/glm;$(SolutionDir)ref/openal/include;$(SolutionDir)ref/dr_libs/include;$(SolutionDir)ref/imgui;$(SolutionDir)ref/imgui/examples;$(SolutionDir)ref/python/include;$(SolutionDir)ref/libserialport/include;$(SolutionDir)ref/asio/include;$(SolutionDir)ref/stb;%(AdditionalIncludeDirectories) Use true false @@ -128,10 +128,10 @@ - WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + WIN32;WIN32_LEAN_AND_MEAN;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) Level3 ProgramDatabase - $(SolutionDir);$(SolutionDir)console;$(SolutionDir)mczapkie;$(SolutionDir)ref/glad/include;$(SolutionDir)ref/glfw/include;$(SolutionDir)ref/glm;$(SolutionDir)ref/openal/include;$(SolutionDir)ref/dr_libs/include;$(SolutionDir)ref/imgui;$(SolutionDir)ref/imgui/examples;$(SolutionDir)ref/python/include;$(SolutionDir)ref/libserialport/include;$(SolutionDir)ref/stb;%(AdditionalIncludeDirectories) + $(SolutionDir);$(SolutionDir)console;$(SolutionDir)mczapkie;$(SolutionDir)ref/glad/include;$(SolutionDir)ref/glfw/include;$(SolutionDir)ref/glm;$(SolutionDir)ref/openal/include;$(SolutionDir)ref/dr_libs/include;$(SolutionDir)ref/imgui;$(SolutionDir)ref/imgui/examples;$(SolutionDir)ref/python/include;$(SolutionDir)ref/libserialport/include;$(SolutionDir)ref/asio/include;$(SolutionDir)ref/stb;%(AdditionalIncludeDirectories) Use true true @@ -151,10 +151,10 @@ - WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + WIN32;WIN32_LEAN_AND_MEAN;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) Level3 ProgramDatabase - $(SolutionDir);$(SolutionDir)console;$(SolutionDir)mczapkie;$(SolutionDir)ref/glad/include;$(SolutionDir)ref/glew/include;$(SolutionDir)ref/glfw/include;$(SolutionDir)ref/glm;$(SolutionDir)ref/openal/include;$(SolutionDir)ref/dr_libs/include;$(SolutionDir)ref/imgui;$(SolutionDir)ref/imgui/examples;$(SolutionDir)ref/python/include;$(SolutionDir)ref/libserialport/include;$(SolutionDir)ref/stb;%(AdditionalIncludeDirectories) + $(SolutionDir);$(SolutionDir)console;$(SolutionDir)mczapkie;$(SolutionDir)ref/glad/include;$(SolutionDir)ref/glfw/include;$(SolutionDir)ref/glm;$(SolutionDir)ref/openal/include;$(SolutionDir)ref/dr_libs/include;$(SolutionDir)ref/imgui;$(SolutionDir)ref/imgui/examples;$(SolutionDir)ref/python/include;$(SolutionDir)ref/libserialport/include;$(SolutionDir)ref/asio/include;$(SolutionDir)ref/stb;%(AdditionalIncludeDirectories) Use true true @@ -234,6 +234,10 @@ + + + + @@ -409,6 +413,10 @@ + + + + diff --git a/maszyna.vcxproj.filters b/maszyna.vcxproj.filters index cee574f7..946b3115 100644 --- a/maszyna.vcxproj.filters +++ b/maszyna.vcxproj.filters @@ -70,6 +70,18 @@ {dd00198e-a316-4bcc-a4d3-916c8dcfe08f} + + {a41f784b-da5a-40de-a4ec-f9b1da1aa607} + + + {5a6c58fe-4b3c-4a46-8ef0-71d4c08d7ebe} + + + {de8b7596-9589-4320-9008-15e15ebf1d8f} + + + {db0ac2d6-9a5d-412e-87b6-8b23c8f8b7a7} + @@ -441,6 +453,18 @@ Source Files\gfx + + Source Files\application\network + + + Source Files\application\network + + + Source Files\application\network + + + Source Files\application\network\backend + @@ -812,6 +836,18 @@ Header Files\gfx + + Header Files\application\network + + + Header Files\application\network + + + Header Files\application\network + + + Header Files\application\network\backend + diff --git a/messaging.cpp b/messaging.cpp index d51f72e4..65a76b79 100644 --- a/messaging.cpp +++ b/messaging.cpp @@ -68,7 +68,8 @@ OnCommandGet(multiplayer::DaneRozkaz *pRozkaz) || ( typeid( *event ) == typeid( lights_event ) ) || ( event->m_sibling != 0 ) ) { // tylko jawne albo niejawne Multiple - simulation::Events.AddToQuery( event, nullptr ); // drugi parametr to dynamic wywołujący - tu brak + command_relay relay; + relay.post(user_command::queueevent, 0.0, 0.0, GLFW_PRESS, 0, glm::vec3(0.0f), &event->name()); } } } @@ -139,7 +140,7 @@ OnCommandGet(multiplayer::DaneRozkaz *pRozkaz) // jeśli długość nazwy jest niezerowa szukamy pierwszego pojazdu o takiej nazwie i odsyłamy parametry ramką #7 auto *vehicle = ( pRozkaz->cString[ 1 ] == '*' ? - simulation::Vehicles.find( Global.asHumanCtrlVehicle ) : + simulation::Vehicles.find( Global.local_start_vehicle ) : simulation::Vehicles.find( std::string{ pRozkaz->cString + 1, (unsigned)pRozkaz->cString[ 0 ] } ) ); if( vehicle != nullptr ) { WyslijNamiary( vehicle ); // wysłanie informacji o pojeździe @@ -180,7 +181,7 @@ OnCommandGet(multiplayer::DaneRozkaz *pRozkaz) { // szukamy pierwszego pojazdu o takiej nazwie i odsyłamy parametry ramką #13 auto *lookup = ( pRozkaz->cString[ 2 ] == '*' ? - simulation::Vehicles.find( Global.asHumanCtrlVehicle ) : // nazwa pojazdu użytkownika + simulation::Vehicles.find( Global.local_start_vehicle ) : // nazwa pojazdu użytkownika simulation::Vehicles.find( std::string( pRozkaz->cString + 2, (unsigned)pRozkaz->cString[ 1 ] ) ) ); // nazwa pojazdu if( lookup == nullptr ) { break; } // nothing found, nothing to do auto *d { lookup }; diff --git a/network/backend/asio.cpp b/network/backend/asio.cpp new file mode 100644 index 00000000..f98f1323 --- /dev/null +++ b/network/backend/asio.cpp @@ -0,0 +1,244 @@ +#include "stdafx.h" +#include "network/backend/asio.h" + +#include "sn_utils.h" +#include "Logs.h" + +network::tcp::connection::connection(asio::io_context &io_ctx, bool client, size_t counter) + : network::connection(client, counter), + m_socket(io_ctx) +{ + m_header_buffer.resize(8); +} + +network::tcp::connection::~connection() +{ + m_socket.shutdown(m_socket.shutdown_both); + m_socket.close(); +} + +void network::tcp::connection::disconnect() +{ + network::connection::disconnect(); +} + +void network::tcp::connection::send_data(std::shared_ptr buffer) +{ + asio::async_write(m_socket, asio::buffer(*buffer.get()), std::bind(&connection::send_complete, this, buffer)); +} + +void network::tcp::connection::connected() +{ + network::connection::connected(); + read_header(); +} + +void network::tcp::connection::read_header() +{ + asio::async_read(m_socket, asio::buffer(m_header_buffer), + std::bind(&connection::handle_header, this, + std::placeholders::_1, std::placeholders::_2)); +} + +void network::tcp::connection::handle_header(const asio::error_code &err, size_t bytes_transferred) +{ + if (err) { + disconnect(); + return; + } + + std::istringstream header(m_header_buffer); + if (m_header_buffer.size() != bytes_transferred) { + disconnect(); + return; + } + + uint32_t sig = sn_utils::ld_uint32(header); + if (sig != NETWORK_MAGIC) { + disconnect(); + return; + } + + uint32_t len = sn_utils::ld_uint32(header); + m_body_buffer.resize(len); + if (len > MAX_MSG_SIZE) { + disconnect(); + return; + } + + asio::async_read(m_socket, asio::buffer(m_body_buffer), + std::bind(&connection::handle_data, this, std::placeholders::_1, std::placeholders::_2)); +} + +void network::tcp::connection::handle_data(const asio::error_code &err, size_t bytes_transferred) +{ + if (err) { + disconnect(); + return; + } + + if (m_body_buffer.size() != bytes_transferred) { + disconnect(); + return; + } + + std::istringstream stream(m_body_buffer); + std::shared_ptr msg = deserialize_message(stream); + if (message_handler) + message_handler(*msg); + + read_header(); +} + +void network::tcp::connection::write_message(const message &msg, std::ostream &stream) +{ + size_t beg = (size_t)stream.tellp(); + + sn_utils::ls_uint32(stream, NETWORK_MAGIC); + sn_utils::ls_uint32(stream, 0); + + serialize_message(msg, stream); + + size_t size = (size_t)stream.tellp() - beg - 8; + if (size > MAX_MSG_SIZE) { + ErrorLog("net: message too big", logtype::net); + return; + } + stream.seekp(beg + 4, std::ios_base::beg); + sn_utils::ls_uint32(stream, size); + + stream.seekp(0, std::ios_base::end); +} + +void network::tcp::connection::send_messages(const std::vector > &messages) +{ + if (messages.size() == 0) + return; + + std::ostringstream stream; + for (auto const &msg : messages) + write_message(*msg.get(), stream); + + stream.flush(); + send_data(std::make_shared(stream.str())); +} + +void network::tcp::connection::send_message(const message &msg) +{ + std::ostringstream stream; + write_message(msg, stream); + + stream.flush(); + send_data(std::make_shared(stream.str())); +} + +// ----------------- + +network::tcp::server::server(std::shared_ptr buf, asio::io_context &io_ctx, const std::string &host, uint32_t port) + : network::server(buf), m_acceptor(io_ctx) +{ + auto endpoint = asio::ip::tcp::endpoint(asio::ip::address::from_string(host), port); + m_acceptor.open(endpoint.protocol()); + m_acceptor.set_option(asio::socket_base::reuse_address(true)); + m_acceptor.set_option(asio::ip::tcp::no_delay(true)); + m_acceptor.bind(endpoint); + m_acceptor.listen(10); + + accept_conn(); +} + +void network::tcp::server::accept_conn() +{ + std::shared_ptr conn = std::make_shared(m_acceptor.get_executor().context()); + conn->set_handler(std::bind(&server::handle_message, this, conn, std::placeholders::_1)); + + m_acceptor.async_accept(conn->m_socket, std::bind(&server::handle_accept, this, conn, std::placeholders::_1)); +} + +void network::tcp::server::handle_accept(std::shared_ptr conn, const asio::error_code &err) +{ + if (!err) + { + clients.emplace_back(conn); + conn->connected(); + } + else + { + conn->state = connection::DEAD; + WriteLog(std::string("net: failed to accept client: " + err.message()), logtype::net); + } + + accept_conn(); +} + +// ------------------ + +network::tcp::client::client(asio::io_context &io_ctxx, const std::string &hostarg, uint32_t portarg) + : host(hostarg), port(portarg), io_ctx(io_ctxx) +{ +} + +void network::tcp::client::connect() +{ + if (this->conn) + return; + + std::shared_ptr conn = std::make_shared(io_ctx, true, resume_frame_counter); + conn->set_handler(std::bind(&client::handle_message, this, conn, std::placeholders::_1)); + + asio::ip::tcp::endpoint endpoint( + asio::ip::address::from_string(host), port); + conn->m_socket.open(endpoint.protocol()); + conn->m_socket.set_option(asio::ip::tcp::no_delay(true)); + conn->m_socket.async_connect(endpoint, + std::bind(&client::handle_accept, this, std::placeholders::_1)); + + this->conn = conn; + +} + +void network::tcp::client::handle_accept(const asio::error_code &err) +{ + if (!err) + { + conn->connected(); + } + else + { + WriteLog(std::string("net: failed to connect: " + err.message()), logtype::net); + conn->disconnect(); + } +} + +network::tcp::asio_manager::asio_manager() { + backend_list().emplace("tcp", this); +} + +std::shared_ptr network::tcp::asio_manager::create_server(std::shared_ptr backbuffer, const std::string &conf) { + std::istringstream stream(conf); + + std::string host; + std::getline(stream, host, ':'); + + int port; + stream >> port; + + return std::make_shared(backbuffer, io_context, host, port); +} + +std::shared_ptr network::tcp::asio_manager::create_client(const std::string &conf) { + std::istringstream stream(conf); + + std::string host; + std::getline(stream, host, ':'); + + int port; + stream >> port; + + return std::make_shared(io_context, host, port); +} + +void network::tcp::asio_manager::update() { + io_context.restart(); + io_context.poll(); +} diff --git a/network/backend/asio.h b/network/backend/asio.h new file mode 100644 index 00000000..f78c85ab --- /dev/null +++ b/network/backend/asio.h @@ -0,0 +1,82 @@ +#define ASIO_STANDALONE + +#ifdef DBG_NEW +#undef new +#endif + +#include + +#include "network/network.h" + +namespace network::tcp +{ + const uint32_t NETWORK_MAGIC = 0x37305545; + const uint32_t MAX_MSG_SIZE = 100000; + + class connection : public network::connection + { + friend class server; + friend class client; + + public: + connection(asio::io_context &io_ctx, bool client = false, size_t counter = 0); + ~connection(); + + virtual void connected() override; + virtual void disconnect() override; + virtual void send_messages(const std::vector > &messages) override; + virtual void send_message(const message &msg) override; + + asio::ip::tcp::socket m_socket; + + private: + std::string m_header_buffer; + std::string m_body_buffer; + + void write_message(const message &msg, std::ostream &stream); + void send_data(std::shared_ptr buffer); + void read_header(); + void handle_header(const asio::error_code &err, size_t bytes_transferred); + void handle_data(const asio::error_code &err, size_t bytes_transferred); + }; + + class server : public network::server + { + private: + void accept_conn(); + void handle_accept(std::shared_ptr conn, const asio::error_code &err); + + asio::ip::tcp::acceptor m_acceptor; + + public: + server(std::shared_ptr buf, asio::io_context &io_ctx, const std::string &host, uint32_t port); + }; + + class client : public network::client + { + private: + void handle_accept(const asio::error_code &err); + asio::io_context &io_ctx; + std::string host; + uint32_t port; + + protected: + virtual void connect() override; + + public: + client(asio::io_context &io_ctx, const std::string &host, uint32_t port); + }; + + class asio_manager : public network::backend_manager { + asio::io_context io_context; + + public: + asio_manager(); + + virtual std::shared_ptr create_server(std::shared_ptr, const std::string &conf) override; + virtual std::shared_ptr create_client(const std::string &conf) override; + virtual void update() override; + }; + + asio_manager manager; +} diff --git a/network/manager.cpp b/network/manager.cpp new file mode 100644 index 00000000..82b9106e --- /dev/null +++ b/network/manager.cpp @@ -0,0 +1,89 @@ +/* +This Source Code Form is subject to the +terms of the Mozilla Public License, v. +2.0. If a copy of the MPL was not +distributed with this file, You can +obtain one at +http://mozilla.org/MPL/2.0/. +*/ + +#include "stdafx.h" +#include "network/manager.h" + +#include "simulation.h" +#include "Logs.h" + +network::server_manager::server_manager() +{ + backbuffer = std::make_shared("backbuffer.bin", std::ios::out | std::ios::in | std::ios::trunc | std::ios::binary); +} + +command_queue::commands_map network::server_manager::pop_commands() +{ + command_queue::commands_map map; + + for (auto srv : servers) + add_to_dequemap(map, srv->pop_commands()); + + return map; +} + +void network::server_manager::push_delta(double render_dt, double dt, double sync, const command_queue::commands_map &commands) +{ + if (dt == 0.0 && commands.empty()) + return; + + frame_info msg; + msg.render_dt = render_dt; + msg.dt = dt; + msg.sync = sync; + msg.commands = commands; + + for (auto srv : servers) + srv->push_delta(msg); + + serialize_message(msg, *backbuffer.get()); +} + +void network::server_manager::create_server(const std::string &backend, const std::string &conf) +{ + auto it = backend_list().find(backend); + if (it == backend_list().end()) { + ErrorLog("net: unknown backend: " + backend); + return; + } + + servers.emplace_back(it->second->create_server(backbuffer, conf)); +} + +network::manager::manager() +{ +} + +void network::manager::update() +{ + for (auto &backend : backend_list()) + backend.second->update(); + + if (client) + client->update(); +} + +void network::manager::create_server(const std::string &backend, const std::string &conf) +{ + servers.emplace(); + servers->create_server(backend, conf); +} + +void network::manager::connect(const std::string &backend, const std::string &conf) +{ + auto it = backend_list().find(backend); + if (it == backend_list().end()) { + ErrorLog("net: unknown backend: " + backend); + return; + } + + client = it->second->create_client(conf); +} + +//std::unordered_map> network::backend_list; diff --git a/network/manager.h b/network/manager.h new file mode 100644 index 00000000..a2ecd91d --- /dev/null +++ b/network/manager.h @@ -0,0 +1,34 @@ +#pragma once +#include +#include "network/network.h" +#include "command.h" + +namespace network +{ + class server_manager + { + private: + std::vector> servers; + std::shared_ptr backbuffer; + + public: + server_manager(); + + void push_delta(double render_dt, double dt, double sync, const command_queue::commands_map &commands); + command_queue::commands_map pop_commands(); + void create_server(const std::string &backend, const std::string &conf); + }; + + class manager + { + public: + manager(); + + std::optional servers; + std::shared_ptr client; + + void create_server(const std::string &backend, const std::string &conf); + void connect(const std::string &backend, const std::string &conf); + void update(); + }; +} diff --git a/network/message.cpp b/network/message.cpp new file mode 100644 index 00000000..03de9fae --- /dev/null +++ b/network/message.cpp @@ -0,0 +1,124 @@ +#include "stdafx.h" +#include "network/message.h" +#include "sn_utils.h" + +void network::client_hello::serialize(std::ostream &stream) const +{ + sn_utils::ls_int32(stream, version); + sn_utils::ls_uint32(stream, start_packet); +} + +void network::client_hello::deserialize(std::istream &stream) +{ + version = sn_utils::ld_int32(stream); + start_packet = sn_utils::ld_uint32(stream); +} + +void network::server_hello::serialize(std::ostream &stream) const +{ + sn_utils::ls_uint32(stream, seed); + sn_utils::ls_int64(stream, timestamp); +} + +void network::server_hello::deserialize(std::istream &stream) +{ + seed = sn_utils::ld_uint32(stream); + timestamp = sn_utils::ld_int64(stream); +} + +void ::network::request_command::serialize(std::ostream &stream) const +{ + sn_utils::ls_uint32(stream, commands.size()); + for (auto const &kv : commands) + { + sn_utils::ls_uint32(stream, kv.first); + sn_utils::ls_uint32(stream, kv.second.size()); + for (command_data const &data : kv.second) + { + sn_utils::ls_uint32(stream, (uint32_t)data.command); + sn_utils::ls_int32(stream, data.action); + sn_utils::ls_float64(stream, data.param1); + sn_utils::ls_float64(stream, data.param2); + sn_utils::ls_float64(stream, data.time_delta); + + sn_utils::s_bool(stream, data.freefly); + sn_utils::s_vec3(stream, data.location); + + sn_utils::s_str(stream, data.payload); + } + } +} + +void network::request_command::deserialize(std::istream &stream) +{ + uint32_t commands_size = sn_utils::ld_uint32(stream); + for (uint32_t i = 0; i < commands_size; i++) + { + uint32_t recipient = sn_utils::ld_uint32(stream); + uint32_t sequence_size = sn_utils::ld_uint32(stream); + + command_queue::commanddata_sequence sequence; + for (uint32_t i = 0; i < sequence_size; i++) + { + command_data data; + data.command = (user_command)sn_utils::ld_uint32(stream); + data.action = sn_utils::ld_int32(stream); + data.param1 = sn_utils::ld_float64(stream); + data.param2 = sn_utils::ld_float64(stream); + data.time_delta = sn_utils::ld_float64(stream); + + data.freefly = sn_utils::d_bool(stream); + data.location = sn_utils::d_vec3(stream); + + data.payload = sn_utils::d_str(stream); + + sequence.emplace_back(data); + } + + commands.emplace(recipient, sequence); + } +} + +void network::frame_info::serialize(std::ostream &stream) const +{ + sn_utils::ls_float64(stream, render_dt); + sn_utils::ls_float64(stream, dt); + sn_utils::ls_float64(stream, sync); + + request_command::serialize(stream); +} + +void network::frame_info::deserialize(std::istream &stream) +{ + render_dt = sn_utils::ld_float64(stream); + dt = sn_utils::ld_float64(stream); + sync = sn_utils::ld_float64(stream); + + request_command::deserialize(stream); +} + +std::shared_ptr network::deserialize_message(std::istream &stream) +{ + message::type_e type = (message::type_e)sn_utils::ld_uint16(stream); + + std::shared_ptr msg; + + if (type == message::CLIENT_HELLO) + msg = std::make_shared(); + else if (type == message::SERVER_HELLO) + msg = std::make_shared(); + else if (type == message::FRAME_INFO) + msg = std::make_shared(); + else if (type == message::REQUEST_COMMAND) + msg = std::make_shared(); + + msg->deserialize(stream); + + return msg; +} + +void network::serialize_message(const message &msg, std::ostream &stream) +{ + sn_utils::ls_uint16(stream, (uint16_t)msg.type); + msg.serialize(stream); +} diff --git a/network/message.h b/network/message.h new file mode 100644 index 00000000..aa81a5ed --- /dev/null +++ b/network/message.h @@ -0,0 +1,73 @@ +#pragma once +#include "network/message.h" +#include "command.h" +#include + +namespace network +{ +struct message +{ + enum type_e + { + CLIENT_HELLO = 0, + SERVER_HELLO, + FRAME_INFO, + REQUEST_COMMAND, + TYPE_MAX + }; + + type_e type; + + message(type_e t) : type(t) {} + virtual void serialize(std::ostream &stream) const {} + virtual void deserialize(std::istream &stream) {} +}; + +struct client_hello : public message +{ + client_hello() : message(CLIENT_HELLO) {} + + virtual void serialize(std::ostream &stream) const override; + virtual void deserialize(std::istream &stream) override; + + int32_t version; + uint32_t start_packet; +}; + +struct server_hello : public message +{ + server_hello() : message(SERVER_HELLO) {} + + uint32_t seed; + int64_t timestamp; + + virtual void serialize(std::ostream &stream) const override; + virtual void deserialize(std::istream &stream) override; +}; + +struct request_command : public message +{ + request_command(type_e type) : message(type) {} + request_command() : message(REQUEST_COMMAND) {} + + command_queue::commands_map commands; + + virtual void serialize(std::ostream &stream) const override; + virtual void deserialize(std::istream &stream) override; +}; + +struct frame_info : public request_command +{ + frame_info() : request_command(FRAME_INFO) {} + + double render_dt; + double dt; + double sync; + + virtual void serialize(std::ostream &stream) const override; + virtual void deserialize(std::istream &stream) override; +}; + +std::shared_ptr deserialize_message(std::istream &stream); +void serialize_message(const message &msg, std::ostream &stream); +} // namespace network diff --git a/network/network.cpp b/network/network.cpp new file mode 100644 index 00000000..28f20de1 --- /dev/null +++ b/network/network.cpp @@ -0,0 +1,269 @@ +#include "stdafx.h" +#include "network/network.h" +#include "network/message.h" +#include "Logs.h" +#include "sn_utils.h" +#include "Timer.h" +#include "application.h" +#include "Globals.h" + +namespace network { + +backend_list_t& backend_list() { + // HACK: static initialization order fiasco fix + // NOTE: potential static deinitialization order fiasco + static backend_list_t backend_list; + return backend_list; +} + +} +// connection + +void network::connection::disconnect() { + WriteLog("net: peer dropped", logtype::net); + state = DEAD; +} + +void network::connection::set_handler(std::function handler) { + message_handler = handler; +} + +network::connection::connection(bool client, size_t counter) { + packet_counter = counter; + is_client = client; + state = AWAITING_HELLO; +} + +void network::connection::connected() +{ + WriteLog("net: socket connected", logtype::net); + + if (is_client) { + client_hello msg; + msg.version = 1; + msg.start_packet = packet_counter; + send_message(msg); + } +} + +void network::connection::catch_up() +{ + backbuffer->seekg(backbuffer_pos); + + std::vector> messages; + + for (int i = 0; i < CATCHUP_PACKETS; i++) { + if (backbuffer->peek() == EOF) { + send_messages(messages); + state = ACTIVE; + backbuffer->seekg(0, std::ios_base::end); + return; + } + + auto msg = deserialize_message(*backbuffer.get()); + + if (packet_counter) { + packet_counter--; + i--; // TODO: it would be better to skip frames in chunks + continue; + } + + messages.push_back(msg); + } + + backbuffer_pos = backbuffer->tellg(); + backbuffer->seekg(0, std::ios_base::end); + + send_messages(messages); +} + +void network::connection::send_complete(std::shared_ptr buf) +{ + if (!is_client && state == CATCHING_UP) { + catch_up(); + } +} + +// -------------- + +// server +network::server::server(std::shared_ptr buf) : backbuffer(buf) +{ + +} + +void network::server::push_delta(const frame_info &msg) +{ + for (auto it = clients.begin(); it != clients.end(); ) { + if ((*it)->state == connection::DEAD) { + it = clients.erase(it); + continue; + } + + if ((*it)->state == connection::ACTIVE) + (*it)->send_message(msg); + + it++; + } +} + +command_queue::commands_map network::server::pop_commands() +{ + command_queue::commands_map map(client_commands_queue); + client_commands_queue.clear(); + return map; +} + +void network::server::handle_message(std::shared_ptr conn, const message &msg) +{ + if (msg.type == message::TYPE_MAX) + { + conn->disconnect(); + return; + } + + if (msg.type == message::CLIENT_HELLO) { + auto cmd = dynamic_cast(msg); + + if (cmd.version != 1 // wrong version + || !Global.ready_to_load) { // not ready yet + conn->disconnect(); + return; + } + + server_hello reply; + reply.seed = Global.random_seed; + reply.timestamp = Global.starting_timestamp; + conn->state = connection::CATCHING_UP; + conn->backbuffer = backbuffer; + conn->backbuffer_pos = 0; + conn->packet_counter = cmd.start_packet; + + conn->send_message(reply); + + WriteLog("net: client accepted", logtype::net); + } + else if (msg.type == message::REQUEST_COMMAND) { + auto cmd = dynamic_cast(msg); + + for (auto const &kv : cmd.commands) + client_commands_queue.emplace(kv); + } +} + +// ------------ + +void network::client::update() +{ + if (conn && conn->state == connection::DEAD) { + conn.reset(); + } + + if (!conn) { + if (!reconnect_delay) { + connect(); + reconnect_delay = RECONNECT_DELAY_FRAMES; + } + reconnect_delay--; + } +} + +// client +std::tuple network::client::get_next_delta(int counter) +{ + auto now = std::chrono::high_resolution_clock::now(); + if (counter == 1) { + frame_time = now - last_frame; + last_frame = now; + } + + if (delta_queue.empty()) { + // buffer underflow + return std::tuple(0.0, 0.0, command_queue::commands_map()); + } + + + float size = delta_queue.size() - consume_counter; + auto entry = delta_queue.front(); + float mult = entry.render_dt / std::chrono::duration_cast>(frame_time).count(); + + if (counter == 1 && size < MAX_BUFFER_SIZE * 2.0f) { + last_target = last_target * TARGET_MIX + + (std::min(TARGET_MIN + jitteriness * JITTERINESS_MULTIPIER, MAX_BUFFER_SIZE)) * (1.0f - TARGET_MIX); + float diff = size - last_target; + jitteriness = std::max(jitteriness * JITTERINESS_MIX, std::abs(diff)); + + float speed = 1.0f + diff * CONSUME_MULTIPIER; + + consume_counter += speed; + } + + float last_rcv_diff = std::chrono::duration_cast>(now - last_rcv).count(); + + if (size > MAX_BUFFER_SIZE || consume_counter > mult || last_rcv_diff > 1.0f) { + if (consume_counter > mult) { + consume_counter = std::clamp(consume_counter - mult, -MAX_BUFFER_SIZE, MAX_BUFFER_SIZE); + } + + delta_queue.pop(); + + return std::make_tuple(entry.dt, entry.sync, entry.commands); + } else { + // nothing to push + return std::tuple(0.0, 0.0, command_queue::commands_map()); + } +} + +void network::client::send_commands(command_queue::commands_map commands) +{ + if (!conn || conn->state == connection::DEAD || commands.empty()) + return; + // eh, maybe queue lost messages + + request_command msg; + msg.commands = commands; + + conn->send_message(msg); +} + +void network::client::handle_message(std::shared_ptr conn, const message &msg) +{ + if (msg.type >= message::TYPE_MAX) + { + conn->disconnect(); + return; + } + + if (msg.type == message::SERVER_HELLO) { + auto cmd = dynamic_cast(msg); + conn->state = connection::ACTIVE; + + if (!Global.ready_to_load) { + Global.random_seed = cmd.seed; + Global.random_engine.seed(Global.random_seed); + Global.starting_timestamp = cmd.timestamp; + Global.ready_to_load = true; + } else if (Global.random_seed != cmd.seed) { + ErrorLog("net: seed mismatch", logtype::net); + conn->disconnect(); + return; + } + + WriteLog("net: accept received", logtype::net); + } + + if (conn->state != connection::ACTIVE) + return; + + if (msg.type == message::FRAME_INFO) { + resume_frame_counter++; + + auto delta = dynamic_cast(msg); + delta_queue.push(delta); + last_rcv = std::chrono::high_resolution_clock::now(); + } +} + +// -------------- diff --git a/network/network.h b/network/network.h new file mode 100644 index 00000000..e84c7e0f --- /dev/null +++ b/network/network.h @@ -0,0 +1,122 @@ +#pragma once + +#include +#include +#include +#include +#include +#include "network/message.h" +#include "command.h" + +namespace network +{ + //m7todo: separate client/server connection class? + class connection + { + friend class server; + friend class client; + + private: + const int CATCHUP_PACKETS = 300; + + bool is_client; + + protected: + std::shared_ptr backbuffer; + size_t backbuffer_pos; + size_t packet_counter; + + void send_complete(std::shared_ptr buf); + void catch_up(); + + public: + std::function message_handler; + + virtual void connected() = 0; + virtual void send_message(const message &msg) = 0; + virtual void send_messages(const std::vector> &messages) = 0; + + connection(bool client = false, size_t counter = 0); + void set_handler(std::function handler); + + virtual void disconnect() = 0; + + enum peer_state { + AWAITING_HELLO, + CATCHING_UP, + ACTIVE, + DEAD + }; + peer_state state; + }; + + class server + { + private: + std::shared_ptr backbuffer; + + protected: + void handle_message(std::shared_ptr conn, const message &msg); + + std::vector> clients; + + command_queue::commands_map client_commands_queue; + + public: + server(std::shared_ptr buf); + void push_delta(const frame_info &msg); + command_queue::commands_map pop_commands(); + }; + + class client + { + protected: + virtual void connect() = 0; + void handle_message(std::shared_ptr conn, const message &msg); + std::shared_ptr conn; + size_t resume_frame_counter = 0; + size_t reconnect_delay = 0; + + const size_t RECONNECT_DELAY_FRAMES = 60; + const float MAX_BUFFER_SIZE = 60.0f; + const float JITTERINESS_MIX = 0.998f; + const float TARGET_MIN = 2.0f; + const float TARGET_MIX = 0.98f; + const float JITTERINESS_MULTIPIER = 2.0f; + const float CONSUME_MULTIPIER = 0.05f; + + std::queue delta_queue; + + float last_target = 20.0f; + float jitteriness = 1.0f; + float consume_counter = 0.0f; + + std::chrono::high_resolution_clock::time_point last_rcv; + std::chrono::high_resolution_clock::time_point last_frame; + std::chrono::high_resolution_clock::duration frame_time; + + public: + void update(); + std::tuple get_next_delta(int counter); + void send_commands(command_queue::commands_map commands); + int get_frame_counter() { + return resume_frame_counter; + } + int get_awaiting_frames() { + return delta_queue.size(); + } + }; + + class backend_manager + { + public: + virtual std::shared_ptr create_server(std::shared_ptr, const std::string &conf) = 0; + virtual std::shared_ptr create_client(const std::string &conf) = 0; + virtual void update() = 0; + }; + + // HACK: static initialization order fiasco fix +// extern std::unordered_map backend_list; + using backend_list_t = std::unordered_map; + backend_list_t& backend_list(); +} diff --git a/scenarioloadermode.cpp b/scenarioloadermode.cpp index df30c706..95c791b3 100644 --- a/scenarioloadermode.cpp +++ b/scenarioloadermode.cpp @@ -37,20 +37,33 @@ scenarioloader_mode::init() { bool scenarioloader_mode::update() { - WriteLog( "\nLoading scenario \"" + Global.SceneryFile + "\"..." ); + if (!Global.ready_to_load) + // waiting for network connection + return true; - auto timestart = std::chrono::system_clock::now(); + if (!state) { + WriteLog("using simulation seed: " + std::to_string(Global.random_seed), logtype::generic); - if( true == simulation::State.deserialize( Global.SceneryFile ) ) { - WriteLog( "Scenario loading time: " + std::to_string( std::chrono::duration_cast( ( std::chrono::system_clock::now() - timestart ) ).count() ) + " seconds" ); - // TODO: implement and use next mode cue - Application.pop_mode(); - Application.push_mode( eu07_application::mode::driver ); - } - else { - ErrorLog( "Bad init: scenario loading failed" ); - Application.pop_mode(); - } + WriteLog( "\nLoading scenario \"" + Global.SceneryFile + "\"..." ); + + timestart = std::chrono::system_clock::now(); + state = simulation::State.deserialize_begin(Global.SceneryFile); + } + + try { + if (simulation::State.deserialize_continue(state)) + return true; + } + catch (invalid_scenery_exception &e) { + ErrorLog( "Bad init: scenario loading failed" ); + Application.pop_mode(); + } + + WriteLog( "Scenario loading time: " + std::to_string( std::chrono::duration_cast( ( std::chrono::system_clock::now() - timestart ) ).count() ) + " seconds" ); + // TODO: implement and use next mode cue + + Application.pop_mode(); + Application.push_mode( eu07_application::mode::driver ); return true; } @@ -75,6 +88,6 @@ scenarioloader_mode::enter() { void scenarioloader_mode::exit() { - simulation::Time.init(); + simulation::Time.init( Global.starting_timestamp ); simulation::Environment.init(); } diff --git a/scenarioloadermode.h b/scenarioloadermode.h index 630c8655..441ac12a 100644 --- a/scenarioloadermode.h +++ b/scenarioloadermode.h @@ -10,8 +10,11 @@ http://mozilla.org/MPL/2.0/. #pragma once #include "applicationmode.h" +#include "simulation.h" class scenarioloader_mode : public application_mode { + std::shared_ptr state; + std::chrono::system_clock::time_point timestart; public: // constructors @@ -32,6 +35,8 @@ public: // input handlers void on_key( int const Key, int const Scancode, int const Action, int const Mods ) override { ; } + void + on_char( unsigned int const Char ) override { ; } void on_cursor_pos( double const Horizontal, double const Vertical ) override { ; } void @@ -40,4 +45,6 @@ public: on_scroll( double const Xoffset, double const Yoffset ) override { ; } void on_event_poll() override { ; } + bool + is_command_processor() override { return false; } }; diff --git a/scene.cpp b/scene.cpp index 69e84095..1f104e7f 100644 --- a/scene.cpp +++ b/scene.cpp @@ -35,7 +35,7 @@ basic_cell::on_click( TAnimModel const *Instance ) { if( ( launcher->name() == Instance->name() ) && ( glm::length2( launcher->location() - Instance->location() ) < launcher->dRadius ) && ( true == launcher->check_conditions() ) ) { - launch_event( launcher ); + launch_event( launcher, true ); } } } @@ -123,11 +123,13 @@ basic_cell::update_events() { // event launchers for( auto *launcher : m_eventlaunchers ) { - if( ( true == ( launcher->check_activation() && launcher->check_conditions() ) ) - && ( ( launcher->dRadius < 0.0 ) - || ( SquareMagnitude( launcher->location() - Global.pCamera.Pos ) < launcher->dRadius ) ) ) { - - launch_event( launcher ); + if( launcher->check_conditions() + && ( launcher->dRadius < 0.0 + || SquareMagnitude( launcher->location() - Global.pCamera.Pos ) < launcher->dRadius ) ) { + if( launcher->check_activation() ) + launch_event( launcher, true ); + if( launcher->check_activation_key() ) + launch_event( launcher, true ); } } } @@ -608,16 +610,19 @@ basic_cell::create_geometry( gfx::geometrybank_handle const &Bank ) { // executes event assigned to specified launcher void -basic_cell::launch_event( TEventLauncher *Launcher ) { - - WriteLog( "Eventlauncher " + Launcher->name() ); - if( ( true == Global.shiftState ) - && ( Launcher->Event2 != nullptr ) ) { - simulation::Events.AddToQuery( Launcher->Event2, nullptr ); - } - else if( Launcher->Event1 ) { - simulation::Events.AddToQuery( Launcher->Event1, nullptr ); - } +basic_cell::launch_event( TEventLauncher *Launcher, bool local_only ) { + WriteLog( "Eventlauncher: " + Launcher->name() ); + if (!local_only) { + if( Launcher->Event1 ) { + simulation::Events.AddToQuery( Launcher->Event1, nullptr ); + } + } else { + command_relay commandrelay; + if (Global.shiftState && Launcher->Event2 != nullptr) + commandrelay.post(user_command::queueevent, 0.0, 0.0, GLFW_PRESS, 0, glm::vec3(0.0f), &Launcher->Event2->name()); + else if (Launcher->Event1) + commandrelay.post(user_command::queueevent, 0.0, 0.0, GLFW_PRESS, 0, glm::vec3(0.0f), &Launcher->Event1->name()); + } } // adjusts cell bounding area to enclose specified node diff --git a/scene.h b/scene.h index 916dd916..4ec48c05 100644 --- a/scene.h +++ b/scene.h @@ -173,7 +173,7 @@ private: using memorycell_sequence = std::vector; // methods void - launch_event( TEventLauncher *Launcher ); + launch_event(TEventLauncher *Launcher, bool local_only); void enclose_area( scene::basic_node *Node ); // members diff --git a/scenenode.h b/scenenode.h index 06b59356..a4da9a0f 100644 --- a/scenenode.h +++ b/scenenode.h @@ -342,6 +342,10 @@ public: group( scene::group_handle Group ); scene::group_handle group() const; + void + mark_dirty() { m_dirty = true; } + bool + dirty() const { return m_dirty; } protected: // members @@ -351,6 +355,7 @@ protected: double m_rangesquaredmax { 0.0 }; // visibility range, max bool m_visible { true }; // visibility flag std::string m_name; + bool m_dirty { false }; private: // methods diff --git a/simulation.cpp b/simulation.cpp index 036c7cfc..767ddfc0 100644 --- a/simulation.cpp +++ b/simulation.cpp @@ -25,6 +25,7 @@ http://mozilla.org/MPL/2.0/. #include "particles.h" #include "scene.h" #include "Train.h" +#include "application.h" namespace simulation { @@ -37,18 +38,26 @@ powergridsource_table Powergrid; sound_table Sounds; instance_table Instances; vehicle_table Vehicles; +train_table Trains; light_array Lights; particle_manager Particles; scene::basic_region *Region { nullptr }; TTrain *Train { nullptr }; +uint16_t prev_train_id { 0 }; bool is_ready { false }; -bool -state_manager::deserialize( std::string const &Scenariofile ) { +std::shared_ptr +state_manager::deserialize_begin(std::string const &Scenariofile) { - return m_serializer.deserialize( Scenariofile ); + return m_serializer.deserialize_begin( Scenariofile ); +} + +bool +state_manager::deserialize_continue(std::shared_ptr state) { + + return m_serializer.deserialize_continue(state); } // stores class data in specified file, in legacy (text) format @@ -92,9 +101,7 @@ state_manager::init_scripting_interface() { void state_manager::update( double const Deltatime, int Iterationcount ) { // aktualizacja animacji krokiem FPS: dt=krok czasu [s], dt*iter=czas od ostatnich przeliczeń - if (Deltatime == 0.0) { - return; - } + if (Deltatime == 0.0) { return; } auto const totaltime { Deltatime * Iterationcount }; // NOTE: we perform animations first, as they can determine factors like contact with powergrid @@ -166,6 +173,261 @@ state_manager::update_scripting_interface() { *m_scriptinginterface.date = *date; } +void state_manager::process_commands() { + command_data commanddata; + while( Commands.pop( commanddata, (uint32_t)command_target::simulation )) { + if (commanddata.command == user_command::consistreleaser) { + TDynamicObject *found_vehicle = simulation::Vehicles.find(commanddata.payload); + TDynamicObject *vehicle = found_vehicle; + + while (vehicle) { + vehicle->MoverParameters->Hamulec->Releaser(commanddata.action != GLFW_RELEASE ? 1 : 0); + vehicle = vehicle->Next(); + } + + vehicle = found_vehicle; + while (vehicle) { + vehicle->MoverParameters->Hamulec->Releaser(commanddata.action != GLFW_RELEASE ? 1 : 0); + vehicle = vehicle->Prev(); + } + } + + if (commanddata.action == GLFW_RELEASE) + continue; + + if (commanddata.command == user_command::debugtoggle) + DebugModeFlag = !DebugModeFlag; + + if (commanddata.command == user_command::pausetoggle) { + if( Global.iPause & 1 ) { + // jeśli pauza startowa + // odpauzowanie, gdy po wczytaniu miało nie startować + Global.iPause ^= 1; + } + else { + Global.iPause ^= 2; // zmiana stanu zapauzowania + } + } + + if (commanddata.command == user_command::focuspauseset) { + if( commanddata.param1 == 1.0 ) + Global.iPause &= ~4; // odpauzowanie, gdy jest na pierwszym planie + else + Global.iPause |= 4; // włączenie pauzy, gdy nieaktywy + } + + if (commanddata.command == user_command::entervehicle) { + // przesiadka do innego pojazdu + if (!commanddata.freefly) + // only available in free fly mode + continue; + + TDynamicObject *dynamic = std::get( simulation::Region->find_vehicle( commanddata.location, 50, false, false ) ); + + if (!dynamic) + continue; + + TTrain *train = simulation::Trains.find(dynamic->name()); + if (train) + continue; + + train = new TTrain(); + if (train->Init(dynamic)) { + simulation::Trains.insert(train); + } + else { + delete train; + train = nullptr; + } + } + + if (commanddata.command == user_command::queueevent) { + std::istringstream ss(commanddata.payload); + + std::string event_name; + std::string vehicle_name; + std::getline(ss, event_name, '%'); + std::getline(ss, vehicle_name, '%'); + + basic_event *ev = Events.FindEvent(event_name); + TDynamicObject *vehicle = nullptr; + if (!vehicle_name.empty()) + vehicle = simulation::Vehicles.find(vehicle_name); + + if (ev) + Events.AddToQuery(ev, vehicle); + } + + if (commanddata.command == user_command::setlight) { + int light = std::round(commanddata.param1); + float state = commanddata.param2; + TAnimModel *model = simulation::Instances.find(commanddata.payload); + if (model) + model->LightSet(light, state); + } + + if (commanddata.command == user_command::setdatetime) { + int yearday = std::round(commanddata.param1); + int minute = std::round(commanddata.param2 * 60.0); + simulation::Time.set_time(yearday, minute); + + simulation::Environment.compute_season(yearday); + } + + if (commanddata.command == user_command::setweather) { + Global.fFogEnd = commanddata.param1; + Global.Overcast = commanddata.param2; + + simulation::Environment.compute_weather(); + } + + if (commanddata.command == user_command::settemperature) { + Global.AirTemperature = commanddata.param1; + } + + if (commanddata.command == user_command::insertmodel) { + std::istringstream ss(commanddata.payload); + + std::string name; + std::string data; + std::getline(ss, name, ':'); + std::getline(ss, data, ':'); + + TAnimModel *model = simulation::State.create_model(data, name, commanddata.location); + simulation::State.create_eventlauncher("node -1 0 launcher eventlauncher 0 0 0 " + std::to_string(model->radius()) + + " none -10000.0 obstacle_collision end", name + "_snd", commanddata.location); + } + + if (commanddata.command == user_command::deletemodel) { + simulation::State.delete_model(simulation::Instances.find(commanddata.payload)); + simulation::State.delete_eventlauncher(simulation::Events.FindEventlauncher(commanddata.payload + "_snd")); + } + + if (commanddata.command == user_command::globalradiostop) { + simulation::Region->RadioStop( commanddata.location ); + } + + if (commanddata.command == user_command::resetconsist) { + TDynamicObject *found_vehicle = simulation::Vehicles.find(commanddata.payload); + TDynamicObject *vehicle = found_vehicle; + + while (vehicle) { + if (vehicle->Next()) + vehicle = vehicle->Next(); + else + break; + } + + while (vehicle) { + vehicle->MoverParameters->DamageFlag = 0; + vehicle->MoverParameters->EngDmgFlag = 0; + vehicle->MoverParameters->V = 0.0; + vehicle->MoverParameters->DistCounter = 0.0; + vehicle->MoverParameters->WheelFlat = 0.0; + vehicle->MoverParameters->AlarmChainFlag = false; + vehicle->MoverParameters->OffsetTrackH = 0.0; + vehicle->MoverParameters->OffsetTrackV = 0.0; + vehicle = vehicle->Prev(); + } + } + + if (commanddata.command == user_command::fillcompressor) { + TDynamicObject *vehicle = simulation::Vehicles.find(commanddata.payload); + vehicle->MoverParameters->CompressedVolume = 8.0f * vehicle->MoverParameters->VeselVolume; + } + + if (commanddata.command == user_command::dynamicmove) { + TDynamicObject *vehicle = simulation::Vehicles.find(commanddata.payload); + if (vehicle) + vehicle->move_set(commanddata.param1); + } + + if (commanddata.command == user_command::consistteleport) { + std::istringstream ss(commanddata.payload); + + std::string track_name; + std::string vehicle_name; + std::getline(ss, vehicle_name, '%'); + std::getline(ss, track_name, '%'); + + TTrack *track = simulation::Paths.find(track_name); + TDynamicObject *vehicle = simulation::Vehicles.find(vehicle_name); + + while (vehicle) { + if (vehicle->Next()) + vehicle = vehicle->Next(); + else + break; + } + + double offset = 0.0; + + while (vehicle) { + offset += vehicle->MoverParameters->Dim.L; + vehicle->place_on_track(track, offset, false); + vehicle = vehicle->Prev(); + } + } + + if (commanddata.command == user_command::spawntrainset) { + + } + + if (commanddata.command == user_command::pullalarmchain) { + TDynamicObject *vehicle = simulation::Vehicles.find(commanddata.payload); + if (vehicle) + vehicle->MoverParameters->AlarmChainSwitch(true); + } + + if (commanddata.command == user_command::sendaicommand) { + std::istringstream ss(commanddata.payload); + + std::string vehicle_name; + std::string command; + std::getline(ss, vehicle_name, '%'); + std::getline(ss, command, '%'); + + TDynamicObject *vehicle = simulation::Vehicles.find(vehicle_name); + glm::dvec3 location = commanddata.location; + if (vehicle && vehicle->Mechanik) + vehicle->Mechanik->PutCommand(command, commanddata.param1, commanddata.param2, &location); + } + + if (commanddata.command == user_command::quitsimulation) { + Application.queue_quit(); + } + + if (DebugModeFlag) { + if (commanddata.command == user_command::timejump) { + Time.update(commanddata.param1); + } + else if (commanddata.command == user_command::timejumplarge) { + Time.update(20.0 * 60.0); + } + else if (commanddata.command == user_command::timejumpsmall) { + Time.update(5.0 * 60.0); + } + } + } +} + +TAnimModel * state_manager::create_model(const std::string &src, const std::string &name, const glm::dvec3 &position) { + return m_serializer.create_model(src, name, position); +} + +TEventLauncher * state_manager::create_eventlauncher(const std::string &src, const std::string &name, const glm::dvec3 &position) { + return m_serializer.create_eventlauncher(src, name, position); +} + +void state_manager::delete_model(TAnimModel *model) { + Region->erase(model); + Instances.purge(model); +} + +void state_manager::delete_eventlauncher(TEventLauncher *launcher) { + launcher->dRadius = 0.0f; // disable it +} + // passes specified sound to all vehicles within range as a radio message broadcasted on specified channel void radio_message( sound_source *Message, int const Channel ) { diff --git a/simulation.h b/simulation.h index 03d8f8c5..e3c0521d 100644 --- a/simulation.h +++ b/simulation.h @@ -27,9 +27,27 @@ public: update_clocks(); void update_scripting_interface(); - // restores simulation data from specified file. returns: true on success, false otherwise - bool - deserialize( std::string const &Scenariofile ); + // process input commands + void + process_commands(); + // create model from node string + TAnimModel * + create_model(const std::string &src, const std::string &name, const glm::dvec3 &position); + // create eventlauncher from node string + TEventLauncher * + create_eventlauncher(const std::string &src, const std::string &name, const glm::dvec3 &position); + // delete TAnimModel instance + void + delete_model(TAnimModel *model); + // delete TEventLauncher instance + void + delete_eventlauncher(TEventLauncher *launcher); + // starts deserialization from specified file, returns context pointer on success, throws otherwise + std::shared_ptr + deserialize_begin(std::string const &Scenariofile); + // continues deserialization for given context, amount limited by time, returns true if needs to be called again + bool + deserialize_continue(std::shared_ptr state); // stores class data in specified file, in legacy (text) format void export_as_text( std::string const &Scenariofile ) const; @@ -54,14 +72,18 @@ extern powergridsource_table Powergrid; extern sound_table Sounds; extern instance_table Instances; extern vehicle_table Vehicles; +extern train_table Trains; extern light_array Lights; extern particle_manager Particles; extern scene::basic_region *Region; extern TTrain *Train; +extern uint16_t prev_train_id; extern bool is_ready; +struct deserializer_state; + } // simulation //--------------------------------------------------------------------------- diff --git a/simulationstateserializer.cpp b/simulationstateserializer.cpp index 5aadd4a6..090a5c70 100644 --- a/simulationstateserializer.cpp +++ b/simulationstateserializer.cpp @@ -31,8 +31,8 @@ http://mozilla.org/MPL/2.0/. namespace simulation { -bool -state_serializer::deserialize( std::string const &Scenariofile ) { +std::shared_ptr +state_serializer::deserialize_begin( std::string const &Scenariofile ) { // TODO: move initialization to separate routine so we can reuse it SafeDelete( Region ); @@ -40,93 +40,81 @@ state_serializer::deserialize( std::string const &Scenariofile ) { simulation::State.init_scripting_interface(); + // NOTE: for the time being import from text format is a given, since we don't have full binary serialization + std::shared_ptr state = + std::make_shared(Scenariofile, cParser::buffer_FILE, Global.asCurrentSceneryPath, Global.bLoadTraction); + // TODO: check first for presence of serialized binary files // if this fails, fall back on the legacy text format - scene::scratch_data importscratchpad; - importscratchpad.name = Scenariofile; - if( ( true == Global.file_binary_terrain ) - && ( Scenariofile != "$.scn" ) ) { + state->scratchpad.name = Scenariofile; + if( Scenariofile != "$.scn" ) { // compilation to binary file isn't supported for rainsted-created overrides // NOTE: we postpone actual loading of the scene until we process time, season and weather data - importscratchpad.binary.terrain = Region->is_scene( Scenariofile ) ; - } - // NOTE: for the time being import from text format is a given, since we don't have full binary serialization - cParser scenarioparser( Scenariofile, cParser::buffer_FILE, Global.asCurrentSceneryPath, Global.bLoadTraction ); - - if( false == scenarioparser.ok() ) { return false; } - - deserialize( scenarioparser, importscratchpad ); - if( ( true == Global.file_binary_terrain ) - && ( false == importscratchpad.binary.terrain ) - && ( Scenariofile != "$.scn" ) ) { - // if we didn't find usable binary version of the scenario files, create them now for future use - // as long as the scenario file wasn't rainsted-created base file override - Region->serialize( Scenariofile ); + state->scratchpad.binary.terrain = Region->is_scene( Scenariofile ) ; } - return true; + if( false == state->input.ok() ) + throw invalid_scenery_exception(); + + // prepare deserialization function table + // since all methods use the same objects, we can have simple, hard-coded binds or lambdas for the task + using deserializefunction = void( state_serializer::*)(cParser &, scene::scratch_data &); + std::vector< + std::pair< + std::string, + deserializefunction> > functionlist = { + { "area", &state_serializer::deserialize_area }, + { "assignment", &state_serializer::deserialize_assignment }, + { "atmo", &state_serializer::deserialize_atmo }, + { "camera", &state_serializer::deserialize_camera }, + { "config", &state_serializer::deserialize_config }, + { "description", &state_serializer::deserialize_description }, + { "event", &state_serializer::deserialize_event }, +// { "lua", &state_serializer::deserialize_lua }, + { "firstinit", &state_serializer::deserialize_firstinit }, + { "group", &state_serializer::deserialize_group }, + { "endgroup", &state_serializer::deserialize_endgroup }, + { "light", &state_serializer::deserialize_light }, + { "node", &state_serializer::deserialize_node }, + { "origin", &state_serializer::deserialize_origin }, + { "endorigin", &state_serializer::deserialize_endorigin }, + { "rotate", &state_serializer::deserialize_rotate }, + { "sky", &state_serializer::deserialize_sky }, + { "test", &state_serializer::deserialize_test }, + { "time", &state_serializer::deserialize_time }, + { "trainset", &state_serializer::deserialize_trainset }, + { "endtrainset", &state_serializer::deserialize_endtrainset } }; + + for( auto &function : functionlist ) { + state->functionmap.emplace( function.first, std::bind( function.second, this, std::ref( state->input ), std::ref( state->scratchpad ) ) ); + } + + return state; } -// restores class data from provided stream -void -state_serializer::deserialize( cParser &Input, scene::scratch_data &Scratchpad ) { - - // prepare deserialization function table - // since all methods use the same objects, we can have simple, hard-coded binds or lambdas for the task - using deserializefunction = void( state_serializer::*)(cParser &, scene::scratch_data &); - std::vector< - std::pair< - std::string, - deserializefunction> > functionlist = { - { "area", &state_serializer::deserialize_area }, - { "assignment", &state_serializer::deserialize_assignment }, - { "atmo", &state_serializer::deserialize_atmo }, - { "camera", &state_serializer::deserialize_camera }, - { "config", &state_serializer::deserialize_config }, - { "description", &state_serializer::deserialize_description }, - { "event", &state_serializer::deserialize_event }, - { "firstinit", &state_serializer::deserialize_firstinit }, - { "group", &state_serializer::deserialize_group }, - { "endgroup", &state_serializer::deserialize_endgroup }, - { "light", &state_serializer::deserialize_light }, - { "node", &state_serializer::deserialize_node }, - { "origin", &state_serializer::deserialize_origin }, - { "endorigin", &state_serializer::deserialize_endorigin }, - { "rotate", &state_serializer::deserialize_rotate }, - { "sky", &state_serializer::deserialize_sky }, - { "test", &state_serializer::deserialize_test }, - { "time", &state_serializer::deserialize_time }, - { "trainset", &state_serializer::deserialize_trainset }, - { "endtrainset", &state_serializer::deserialize_endtrainset } }; - using deserializefunctionbind = std::function; - std::unordered_map< - std::string, - deserializefunctionbind> functionmap; - for( auto &function : functionlist ) { - functionmap.emplace( function.first, std::bind( function.second, this, std::ref( Input ), std::ref( Scratchpad ) ) ); - } +// continues deserialization for given context, amount limited by time, returns true if needs to be called again +bool +state_serializer::deserialize_continue(std::shared_ptr state) { + cParser &Input = state->input; + scene::scratch_data &Scratchpad = state->scratchpad; // deserialize content from the provided input - auto - timelast { std::chrono::steady_clock::now() }, - timenow { timelast }; + auto timelast { std::chrono::steady_clock::now() }; std::string token { Input.getToken() }; while( false == token.empty() ) { - auto lookup = functionmap.find( token ); - if( lookup != functionmap.end() ) { + auto lookup = state->functionmap.find( token ); + if( lookup != state->functionmap.end() ) { lookup->second(); } else { ErrorLog( "Bad scenario: unexpected token \"" + token + "\" defined in file \"" + Input.Name() + "\" (line " + std::to_string( Input.Line() - 1 ) + ")" ); } - timenow = std::chrono::steady_clock::now(); - if( std::chrono::duration_cast( timenow - timelast ).count() >= 100 ) { - timelast = timenow; - glfwPollEvents(); + auto timenow = std::chrono::steady_clock::now(); + if( std::chrono::duration_cast( timenow - timelast ).count() >= 200 ) { Application.set_progress( Input.getProgress(), Input.getFullProgress() ); - GfxRenderer->Render(); + return true; } token = Input.getToken(); @@ -136,6 +124,18 @@ state_serializer::deserialize( cParser &Input, scene::scratch_data &Scratchpad ) // manually perform scenario initialization deserialize_firstinit( Input, Scratchpad ); } +/* + scene::Groups.update_map(); + Region->create_map_geometry(); +*/ + if( ( false == state->scratchpad.binary.terrain ) + && ( state->scenariofile != "$.scn" ) ) { + // if we didn't find usable binary version of the scenario files, create them now for future use + // as long as the scenario file wasn't rainsted-created base file override + Region->serialize( state->scenariofile ); + } + + return false; } void @@ -1072,6 +1072,60 @@ state_serializer::export_as_text( std::string const &Scenariofile ) const { WriteLog( "Scenery data export done." ); } +TAnimModel *state_serializer::create_model(const std::string &src, const std::string &name, const glm::dvec3 &position) { + cParser parser(src); + parser.getTokens(); // "node" + parser.getTokens(2); // ranges + + scene::node_data nodedata; + parser >> nodedata.range_max >> nodedata.range_min; + + parser.getTokens(2); // name, type + nodedata.name = name; + nodedata.type = "model"; + + scene::scratch_data scratch; + + TAnimModel *cloned = deserialize_model(parser, scratch, nodedata); + + if (!cloned) + return nullptr; + + cloned->mark_dirty(); + cloned->location(position); + simulation::Instances.insert(cloned); + simulation::Region->insert(cloned); + + return cloned; +} + +TEventLauncher *state_serializer::create_eventlauncher(const std::string &src, const std::string &name, const glm::dvec3 &position) { + cParser parser(src); + parser.getTokens(); // "node" + parser.getTokens(2); // ranges + + scene::node_data nodedata; + parser >> nodedata.range_max >> nodedata.range_min; + + parser.getTokens(2); // name, type + nodedata.name = name; + nodedata.type = "eventlauncher"; + + scene::scratch_data scratch; + + TEventLauncher *launcher = deserialize_eventlauncher(parser, scratch, nodedata); + + if (!launcher) + return nullptr; + + launcher->Event1 = simulation::Events.FindEvent( launcher->asEvent1Name ); + launcher->location(position); + simulation::Events.insert(launcher); + simulation::Region->insert(launcher); + + return launcher; +} + } // simulation //--------------------------------------------------------------------------- diff --git a/simulationstateserializer.h b/simulationstateserializer.h index ef39056e..fee8c0f8 100644 --- a/simulationstateserializer.h +++ b/simulationstateserializer.h @@ -14,21 +14,40 @@ http://mozilla.org/MPL/2.0/. namespace simulation { +struct deserializer_state { + std::string scenariofile; + cParser input; + scene::scratch_data scratchpad; + using deserializefunctionbind = std::function; + std::unordered_map< + std::string, + deserializefunctionbind> functionmap; + + deserializer_state(std::string const &File, cParser::buffertype const Type, const std::string &Path, bool const Loadtraction) + : scenariofile(File), input(File, Type, Path, Loadtraction) { } +}; + class state_serializer { public: // methods - // restores simulation data from specified file. returns: true on success, false otherwise - bool - deserialize( std::string const &Scenariofile ); + // starts deserialization from specified file, returns context pointer on success, throws otherwise + std::shared_ptr + deserialize_begin(std::string const &Scenariofile); + // continues deserialization for given context, amount limited by time, returns true if needs to be called again + bool + deserialize_continue(std::shared_ptr state); // stores class data in specified file, in legacy (text) format void export_as_text( std::string const &Scenariofile ) const; + // create new model from node stirng + TAnimModel * create_model(std::string const &src, std::string const &name, const glm::dvec3 &position); + // create new eventlauncher from node stirng + TEventLauncher * create_eventlauncher(std::string const &src, std::string const &name, const glm::dvec3 &position); private: // methods // restores class data from provided stream - void deserialize( cParser &Input, scene::scratch_data &Scratchpad ); void deserialize_area( cParser &Input, scene::scratch_data &Scratchpad ); void deserialize_assignment( cParser &Input, scene::scratch_data &Scratchpad ); void deserialize_atmo( cParser &Input, scene::scratch_data &Scratchpad ); diff --git a/simulationtime.cpp b/simulationtime.cpp index 81981bfa..dd519763 100644 --- a/simulationtime.cpp +++ b/simulationtime.cpp @@ -20,7 +20,7 @@ scenario_time Time; } // simulation void -scenario_time::init() { +scenario_time::init(std::time_t timestamp) { char monthdaycounts[ 2 ][ 13 ] = { { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }, @@ -32,7 +32,21 @@ scenario_time::init() { auto const requestedhour { ( requestedtime / 60 ) % 24 }; auto const requestedminute { requestedtime % 60 }; // cache requested elements, if any - ::GetLocalTime( &m_time ); + + if( timestamp != 0 ) { + std::tm *tms = std::gmtime( ×tamp ); + m_time.wYear = tms->tm_year + 1900; + m_time.wMonth = tms->tm_mon + 1; + m_time.wDayOfWeek = tms->tm_wday; + m_time.wDay = tms->tm_mday; + m_time.wHour = tms->tm_hour; + m_time.wMinute = tms->tm_min; + m_time.wSecond = tms->tm_sec; + m_time.wMilliseconds = 0; + } + else { + ::GetLocalTime( &m_time ); + } if( Global.fMoveLight > 0.f ) { // day and month of the year can be overriden by scenario setup @@ -192,6 +206,13 @@ scenario_time::julian_day() const { return JD; } +void scenario_time::set_time(int yearday, int minute) { + m_yearday = yearday; + daymonth(m_time.wDay, m_time.wMonth, m_time.wYear, m_yearday); + m_time.wHour = minute / 60; + m_time.wMinute = minute % 60; +} + // calculates day of week for provided date int scenario_time::day_of_week( int const Day, int const Month, int const Year ) const { diff --git a/simulationtime.h b/simulationtime.h index 58a8b317..320564e0 100644 --- a/simulationtime.h +++ b/simulationtime.h @@ -16,7 +16,7 @@ public: scenario_time() { m_time.wHour = 10; m_time.wMinute = 30; } void - init(); + init(time_t timestamp = 0); void update( double const Deltatime ); inline @@ -41,6 +41,8 @@ public: double zone_bias() const { return m_timezonebias; } + void + set_time(int yearday, int minute); private: // converts provided time transition date to regular date diff --git a/sn_utils.cpp b/sn_utils.cpp index 9906d9c1..d96dc74a 100644 --- a/sn_utils.cpp +++ b/sn_utils.cpp @@ -38,6 +38,18 @@ int32_t sn_utils::ld_int32(std::istream &s) return reinterpret_cast(v); } +// deserialize little endian int64 +int64_t sn_utils::ld_int64(std::istream &s) +{ + uint8_t buf[8]; + s.read((char*)buf, 8); + uint64_t v = ((uint64_t)buf[7] << 56) | ((uint64_t)buf[6] << 48) | + ((uint64_t)buf[5] << 40) | ((uint64_t)buf[4] << 32) | + ((uint64_t)buf[3] << 24) | ((uint64_t)buf[2] << 16) | + ((uint64_t)buf[1] << 8) | (uint64_t)buf[0]; + return reinterpret_cast(v); +} + // deserialize little endian ieee754 float32 float sn_utils::ld_float32(std::istream &s) { @@ -88,6 +100,14 @@ glm::dvec3 sn_utils::d_dvec3(std::istream& s) ld_float64(s) }; } +glm::vec3 sn_utils::d_vec3( std::istream& s) +{ + return { + ld_float32(s), + ld_float32(s), + ld_float32(s) }; +} + glm::vec4 sn_utils::d_vec4( std::istream& s) { return { @@ -125,6 +145,20 @@ void sn_utils::ls_int32(std::ostream &s, int32_t v) s.write((char*)buf, 4); } +void sn_utils::ls_int64(std::ostream &s, int64_t v) +{ + uint8_t buf[8]; + buf[0] = v; + buf[1] = v >> 8; + buf[2] = v >> 16; + buf[3] = v >> 24; + buf[4] = v >> 32; + buf[5] = v >> 40; + buf[6] = v >> 48; + buf[7] = v >> 56; + s.write((char*)buf, 8); +} + void sn_utils::ls_float32(std::ostream &s, float t) { uint32_t v = reinterpret_cast(t); @@ -173,6 +207,13 @@ void sn_utils::s_dvec3(std::ostream &s, glm::dvec3 const &v) ls_float64(s, v.z); } +void sn_utils::s_vec3(std::ostream &s, glm::vec3 const &v) +{ + ls_float32(s, v.x); + ls_float32(s, v.y); + ls_float32(s, v.z); +} + void sn_utils::s_vec4(std::ostream &s, glm::vec4 const &v) { ls_float32(s, v.x); diff --git a/sn_utils.h b/sn_utils.h index e11d1b31..5b8c8001 100644 --- a/sn_utils.h +++ b/sn_utils.h @@ -10,20 +10,24 @@ public: static uint16_t ld_uint16(std::istream&); static uint32_t ld_uint32(std::istream&); static int32_t ld_int32(std::istream&); + static int64_t ld_int64(std::istream&); static float ld_float32(std::istream&); static double ld_float64(std::istream&); static std::string d_str(std::istream&); static bool d_bool(std::istream&); static glm::dvec3 d_dvec3(std::istream&); + static glm::vec3 d_vec3(std::istream&); static glm::vec4 d_vec4(std::istream&); static void ls_uint16(std::ostream&, uint16_t); static void ls_uint32(std::ostream&, uint32_t); static void ls_int32(std::ostream&, int32_t); + static void ls_int64(std::ostream&, int64_t); static void ls_float32(std::ostream&, float); static void ls_float64(std::ostream&, double); static void s_str(std::ostream&, std::string); static void s_bool(std::ostream&, bool); static void s_dvec3(std::ostream&, glm::dvec3 const &); + static void s_vec3(std::ostream&, glm::vec3 const &); static void s_vec4(std::ostream&, glm::vec4 const &); }; \ No newline at end of file diff --git a/sound.cpp b/sound.cpp index 8a0b722d..67fa09bf 100644 --- a/sound.cpp +++ b/sound.cpp @@ -194,7 +194,7 @@ sound_source::deserialize_mapping( cParser &Input ) { m_pitchvariation = ( variation == 0.0f ? 1.0f : - 0.01f * static_cast( Random( 100.0 - variation, 100.0 + variation ) ) ); + 0.01f * static_cast( LocalRandom( 100.0 - variation, 100.0 + variation ) ) ); } else if( key == "startoffset:" ) { m_startoffset = @@ -353,7 +353,7 @@ sound_source::play( int const Flags ) { // initialize emitter-specific pitch variation if it wasn't yet set if( m_pitchvariation == 0.f ) { - m_pitchvariation = 0.01f * static_cast( Random( 97.5, 102.5 ) ); + m_pitchvariation = 0.01f * static_cast( LocalRandom( 97.5, 102.5 ) ); } /* if( ( ( m_flags & sound_flags::exclusive ) != 0 ) diff --git a/utilities.cpp b/utilities.cpp index 49d6ec53..d93cfd7a 100644 --- a/utilities.cpp +++ b/utilities.cpp @@ -28,7 +28,7 @@ Copyright (C) 2007-2014 Maciej Cierniak #include "parser.h" bool DebugModeFlag = false; -bool FreeFlyModeFlag = false; +bool FreeFlyModeFlag = true; bool EditorModeFlag = false; bool DebugCameraFlag = false; bool DebugTractionFlag = false; @@ -106,8 +106,8 @@ bool ClearFlag( int &Flag, int const Value ) { double Random(double a, double b) { - std::uniform_real_distribution<> dis(a, b); - return dis(Global.random_engine); + uint32_t val = Global.random_engine(); + return interpolate(a, b, (double)val / Global.random_engine.max()); } double LocalRandom(double a, double b) diff --git a/utilities.h b/utilities.h index df682341..e8fd614e 100644 --- a/utilities.h +++ b/utilities.h @@ -313,8 +313,8 @@ template void bounding_box( VecType_ &Mincorner, VecType_ &Maxcorner, Iterator_ First, Iterator_ Last ) { - Mincorner = VecType_( typename std::numeric_limits::max() ); - Maxcorner = VecType_( typename std::numeric_limits::lowest() ); + Mincorner = VecType_( std::numeric_limits::max() ); + Maxcorner = VecType_( std::numeric_limits::lowest() ); std::for_each( First, Last,