16
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-07-22 22:09:19 +02:00

milek7/sim branch network code import

This commit is contained in:
tmj-fstate
2020-02-16 03:03:17 +01:00
parent 2ce3091e8f
commit 7a0c89f508
56 changed files with 2609 additions and 426 deletions

View File

@@ -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

View File

@@ -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 ) {

View File

@@ -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 );
/*

View File

@@ -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 ];

View File

@@ -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;

View File

@@ -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();

View File

@@ -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());
}
}
}
}

View File

@@ -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<TEventLauncher> m_inputdrivenlaunchers;
basic_table<TEventLauncher> m_radiodrivenlaunchers;
eventlauncher_sequence m_launcherqueue;
command_relay m_relay;
};

View File

@@ -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<float>(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);

View File

@@ -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<unsigned int>( std::time( NULL ) ) ) };
std::mt19937 local_random_engine{ std::mt19937( static_cast<unsigned int>( 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<float> 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<std::pair<std::string, std::string>> network_servers;
std::optional<std::pair<std::string, std::string>> network_client;
double desync = 0.0;
// methods
void LoadIniFile( std::string asFileName );

2
Logs.h
View File

@@ -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 );

37
Names.h
View File

@@ -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 {

View File

@@ -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

16
Timer.h
View File

@@ -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<float, std::milli>
stop() {
m_accumulator = 0.95f * m_accumulator + std::chrono::duration_cast<std::chrono::microseconds>( ( std::chrono::steady_clock::now() - m_start ) ).count() / 1000.f; }
m_last = std::chrono::duration_cast<std::chrono::microseconds>( ( 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<std::chrono::steady_clock> 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 {

166
Train.cpp
View File

@@ -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<std::size_t>( command_target::vehicle ) | 0 ) ) {
while( simulation::Commands.pop( commanddata, static_cast<std::size_t>( 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());
}
}
}

16
Train.h
View File

@@ -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<std::pair<std::string, texture_handle>> 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<TTrain> {
public:
void update( double dt );
};
//---------------------------------------------------------------------------

View File

@@ -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)

View File

@@ -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

View File

@@ -43,6 +43,11 @@ http://mozilla.org/MPL/2.0/.
#pragma comment (lib, "dbghelp.lib")
#pragma comment (lib, "version.lib")
#ifdef __unix__
#include <unistd.h>
#include <sys/stat.h>
#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;
}

View File

@@ -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<GLFWwindow *> m_windows;
std::optional<network::manager> m_network;
};
extern eu07_application Application;

View File

@@ -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

View File

@@ -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<std::size_t>( 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<std::size_t>( 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<uint32_t>( 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);
}
//---------------------------------------------------------------------------

View File

@@ -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<command_data> commanddata_sequence;
typedef std::unordered_map<uint32_t, commanddata_sequence> 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<command_data> commanddata_sequence;
typedef std::unordered_map<std::size_t, commanddata_sequence> 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<typename A, typename B>
void add_to_dequemap(std::unordered_map<A, std::deque<B>> &lhs, const std::unordered_map<A, std::deque<B>> &rhs) {
for (auto const &kv : rhs) {
auto lookup = lhs.emplace(kv.first, std::deque<B>());
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

View File

@@ -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 }
};
}

View File

@@ -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<std::uintptr_t>( simulation::Train->Dynamic() );
else
TSubModel::iInstance = 0;
if( simulation::Train != nullptr ) {
TSubModel::iInstance = reinterpret_cast<std::uintptr_t>( 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<TDynamicObject *>( 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
{

View File

@@ -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
};

View File

@@ -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();
}

View File

@@ -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 {

View File

@@ -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

View File

@@ -84,10 +84,10 @@
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions>WIN32;WIN32_LEAN_AND_MEAN;_DEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<WarningLevel>Level3</WarningLevel>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<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/stb;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<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)</AdditionalIncludeDirectories>
<PrecompiledHeader>Use</PrecompiledHeader>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<MinimalRebuild>false</MinimalRebuild>
@@ -108,10 +108,10 @@
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions>WIN32;WIN32_LEAN_AND_MEAN;_DEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<WarningLevel>Level3</WarningLevel>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<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/stb;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<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)</AdditionalIncludeDirectories>
<PrecompiledHeader>Use</PrecompiledHeader>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<MinimalRebuild>false</MinimalRebuild>
@@ -128,10 +128,10 @@
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions>WIN32;WIN32_LEAN_AND_MEAN;NDEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<WarningLevel>Level3</WarningLevel>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<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/stb;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<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)</AdditionalIncludeDirectories>
<PrecompiledHeader>Use</PrecompiledHeader>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<WholeProgramOptimization>true</WholeProgramOptimization>
@@ -151,10 +151,10 @@
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions>WIN32;WIN32_LEAN_AND_MEAN;NDEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<WarningLevel>Level3</WarningLevel>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<AdditionalIncludeDirectories>$(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)</AdditionalIncludeDirectories>
<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)</AdditionalIncludeDirectories>
<PrecompiledHeader>Use</PrecompiledHeader>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<WholeProgramOptimization>true</WholeProgramOptimization>
@@ -234,6 +234,10 @@
<ClCompile Include="drivermouseinput.cpp" />
<ClCompile Include="mtable.cpp" />
<ClCompile Include="Names.cpp" />
<ClCompile Include="network\backend\asio.cpp" />
<ClCompile Include="network\manager.cpp" />
<ClCompile Include="network\message.cpp" />
<ClCompile Include="network\network.cpp" />
<ClCompile Include="opengl33geometrybank.cpp" />
<ClCompile Include="opengl33light.cpp" />
<ClCompile Include="opengl33particles.cpp" />
@@ -409,6 +413,10 @@
<ClInclude Include="drivermouseinput.h" />
<ClInclude Include="mtable.h" />
<ClInclude Include="Names.h" />
<ClInclude Include="network\backend\asio.h" />
<ClInclude Include="network\manager.h" />
<ClInclude Include="network\message.h" />
<ClInclude Include="network\network.h" />
<ClInclude Include="opengl33geometrybank.h" />
<ClInclude Include="opengl33light.h" />
<ClInclude Include="opengl33particles.h" />

View File

@@ -70,6 +70,18 @@
<Filter Include="Source Files\math">
<UniqueIdentifier>{dd00198e-a316-4bcc-a4d3-916c8dcfe08f}</UniqueIdentifier>
</Filter>
<Filter Include="Source Files\application\network">
<UniqueIdentifier>{a41f784b-da5a-40de-a4ec-f9b1da1aa607}</UniqueIdentifier>
</Filter>
<Filter Include="Header Files\application\network">
<UniqueIdentifier>{5a6c58fe-4b3c-4a46-8ef0-71d4c08d7ebe}</UniqueIdentifier>
</Filter>
<Filter Include="Header Files\application\network\backend">
<UniqueIdentifier>{de8b7596-9589-4320-9008-15e15ebf1d8f}</UniqueIdentifier>
</Filter>
<Filter Include="Source Files\application\network\backend">
<UniqueIdentifier>{db0ac2d6-9a5d-412e-87b6-8b23c8f8b7a7}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="AirCoupler.cpp">
@@ -441,6 +453,18 @@
<ClCompile Include="opengl33geometrybank.cpp">
<Filter>Source Files\gfx</Filter>
</ClCompile>
<ClCompile Include="network\network.cpp">
<Filter>Source Files\application\network</Filter>
</ClCompile>
<ClCompile Include="network\manager.cpp">
<Filter>Source Files\application\network</Filter>
</ClCompile>
<ClCompile Include="network\message.cpp">
<Filter>Source Files\application\network</Filter>
</ClCompile>
<ClCompile Include="network\backend\asio.cpp">
<Filter>Source Files\application\network\backend</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="Globals.h">
@@ -812,6 +836,18 @@
<ClInclude Include="stars.h">
<Filter>Header Files\gfx</Filter>
</ClInclude>
<ClInclude Include="network\manager.h">
<Filter>Header Files\application\network</Filter>
</ClInclude>
<ClInclude Include="network\message.h">
<Filter>Header Files\application\network</Filter>
</ClInclude>
<ClInclude Include="network\network.h">
<Filter>Header Files\application\network</Filter>
</ClInclude>
<ClInclude Include="network\backend\asio.h">
<Filter>Header Files\application\network\backend</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="maszyna.rc">

View File

@@ -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 };

244
network/backend/asio.cpp Normal file
View File

@@ -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<std::string> 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<message> 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<std::shared_ptr<message> > &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<std::string>(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<std::string>(stream.str()));
}
// -----------------
network::tcp::server::server(std::shared_ptr<std::istream> 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<connection> conn = std::make_shared<connection>(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<connection> 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<connection> conn = std::make_shared<connection>(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::server> network::tcp::asio_manager::create_server(std::shared_ptr<std::fstream> backbuffer, const std::string &conf) {
std::istringstream stream(conf);
std::string host;
std::getline(stream, host, ':');
int port;
stream >> port;
return std::make_shared<tcp::server>(backbuffer, io_context, host, port);
}
std::shared_ptr<network::client> 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<tcp::client>(io_context, host, port);
}
void network::tcp::asio_manager::update() {
io_context.restart();
io_context.poll();
}

82
network/backend/asio.h Normal file
View File

@@ -0,0 +1,82 @@
#define ASIO_STANDALONE
#ifdef DBG_NEW
#undef new
#endif
#include <asio.hpp>
#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<std::shared_ptr<message> > &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<std::string> 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<connection> conn, const asio::error_code &err);
asio::ip::tcp::acceptor m_acceptor;
public:
server(std::shared_ptr<std::istream> 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<network::server> create_server(std::shared_ptr<std::fstream>, const std::string &conf) override;
virtual std::shared_ptr<network::client> create_client(const std::string &conf) override;
virtual void update() override;
};
asio_manager manager;
}

89
network/manager.cpp Normal file
View File

@@ -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<std::fstream>("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<std::string, std::shared_ptr<network::backend_manager>> network::backend_list;

34
network/manager.h Normal file
View File

@@ -0,0 +1,34 @@
#pragma once
#include <memory>
#include "network/network.h"
#include "command.h"
namespace network
{
class server_manager
{
private:
std::vector<std::shared_ptr<server>> servers;
std::shared_ptr<std::fstream> 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<server_manager> servers;
std::shared_ptr<network::client> client;
void create_server(const std::string &backend, const std::string &conf);
void connect(const std::string &backend, const std::string &conf);
void update();
};
}

124
network/message.cpp Normal file
View File

@@ -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::message> network::deserialize_message(std::istream &stream)
{
message::type_e type = (message::type_e)sn_utils::ld_uint16(stream);
std::shared_ptr<message> msg;
if (type == message::CLIENT_HELLO)
msg = std::make_shared<client_hello>();
else if (type == message::SERVER_HELLO)
msg = std::make_shared<server_hello>();
else if (type == message::FRAME_INFO)
msg = std::make_shared<frame_info>();
else if (type == message::REQUEST_COMMAND)
msg = std::make_shared<request_command>();
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);
}

73
network/message.h Normal file
View File

@@ -0,0 +1,73 @@
#pragma once
#include "network/message.h"
#include "command.h"
#include <queue>
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<message> deserialize_message(std::istream &stream);
void serialize_message(const message &msg, std::ostream &stream);
} // namespace network

269
network/network.cpp Normal file
View File

@@ -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<void (const message &)> 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<std::shared_ptr<message>> 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<std::string> buf)
{
if (!is_client && state == CATCHING_UP) {
catch_up();
}
}
// --------------
// server
network::server::server(std::shared_ptr<std::istream> 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<connection> conn, const message &msg)
{
if (msg.type == message::TYPE_MAX)
{
conn->disconnect();
return;
}
if (msg.type == message::CLIENT_HELLO) {
auto cmd = dynamic_cast<const client_hello&>(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<const request_command&>(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<double, double, command_queue::commands_map> 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<double, double,
command_queue::commands_map>(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<std::chrono::duration<float>>(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<std::chrono::duration<float>>(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<double, double,
command_queue::commands_map>(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<connection> conn, const message &msg)
{
if (msg.type >= message::TYPE_MAX)
{
conn->disconnect();
return;
}
if (msg.type == message::SERVER_HELLO) {
auto cmd = dynamic_cast<const server_hello&>(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<const frame_info&>(msg);
delta_queue.push(delta);
last_rcv = std::chrono::high_resolution_clock::now();
}
}
// --------------

122
network/network.h Normal file
View File

@@ -0,0 +1,122 @@
#pragma once
#include <memory>
#include <functional>
#include <optional>
#include <queue>
#include <chrono>
#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<std::istream> backbuffer;
size_t backbuffer_pos;
size_t packet_counter;
void send_complete(std::shared_ptr<std::string> buf);
void catch_up();
public:
std::function<void(const message &msg)> message_handler;
virtual void connected() = 0;
virtual void send_message(const message &msg) = 0;
virtual void send_messages(const std::vector<std::shared_ptr<message>> &messages) = 0;
connection(bool client = false, size_t counter = 0);
void set_handler(std::function<void(const message &msg)> handler);
virtual void disconnect() = 0;
enum peer_state {
AWAITING_HELLO,
CATCHING_UP,
ACTIVE,
DEAD
};
peer_state state;
};
class server
{
private:
std::shared_ptr<std::istream> backbuffer;
protected:
void handle_message(std::shared_ptr<connection> conn, const message &msg);
std::vector<std::shared_ptr<connection>> clients;
command_queue::commands_map client_commands_queue;
public:
server(std::shared_ptr<std::istream> 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<connection> conn, const message &msg);
std::shared_ptr<connection> 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<frame_info> 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<double, double, command_queue::commands_map> 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<server> create_server(std::shared_ptr<std::fstream>, const std::string &conf) = 0;
virtual std::shared_ptr<client> create_client(const std::string &conf) = 0;
virtual void update() = 0;
};
// HACK: static initialization order fiasco fix
// extern std::unordered_map<std::string, backend_manager*> backend_list;
using backend_list_t = std::unordered_map<std::string, backend_manager*>;
backend_list_t& backend_list();
}

View File

@@ -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::seconds>( ( 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::seconds>( ( 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();
}

View File

@@ -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<simulation::deserializer_state> 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; }
};

View File

@@ -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

View File

@@ -173,7 +173,7 @@ private:
using memorycell_sequence = std::vector<TMemCell *>;
// methods
void
launch_event( TEventLauncher *Launcher );
launch_event(TEventLauncher *Launcher, bool local_only);
void
enclose_area( scene::basic_node *Node );
// members

View File

@@ -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

View File

@@ -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<deserializer_state>
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<deserializer_state> 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<TDynamicObject *>( 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 ) {

View File

@@ -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<deserializer_state>
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<deserializer_state> 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
//---------------------------------------------------------------------------

View File

@@ -31,8 +31,8 @@ http://mozilla.org/MPL/2.0/.
namespace simulation {
bool
state_serializer::deserialize( std::string const &Scenariofile ) {
std::shared_ptr<deserializer_state>
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<deserializer_state> state =
std::make_shared<deserializer_state>(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<void()>;
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<deserializer_state> 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<std::string>() };
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<std::chrono::milliseconds>( timenow - timelast ).count() >= 100 ) {
timelast = timenow;
glfwPollEvents();
auto timenow = std::chrono::steady_clock::now();
if( std::chrono::duration_cast<std::chrono::milliseconds>( timenow - timelast ).count() >= 200 ) {
Application.set_progress( Input.getProgress(), Input.getFullProgress() );
GfxRenderer->Render();
return true;
}
token = Input.getToken<std::string>();
@@ -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
//---------------------------------------------------------------------------

View File

@@ -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<void()>;
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<deserializer_state>
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<deserializer_state> 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 );

View File

@@ -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( &timestamp );
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 {

View File

@@ -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

View File

@@ -38,6 +38,18 @@ int32_t sn_utils::ld_int32(std::istream &s)
return reinterpret_cast<int32_t&>(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<int64_t&>(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<uint32_t&>(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);

View File

@@ -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 &);
};

View File

@@ -194,7 +194,7 @@ sound_source::deserialize_mapping( cParser &Input ) {
m_pitchvariation = (
variation == 0.0f ?
1.0f :
0.01f * static_cast<float>( Random( 100.0 - variation, 100.0 + variation ) ) );
0.01f * static_cast<float>( 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<float>( Random( 97.5, 102.5 ) );
m_pitchvariation = 0.01f * static_cast<float>( LocalRandom( 97.5, 102.5 ) );
}
/*
if( ( ( m_flags & sound_flags::exclusive ) != 0 )

View File

@@ -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)

View File

@@ -313,8 +313,8 @@ template <class Iterator_, class VecType_>
void
bounding_box( VecType_ &Mincorner, VecType_ &Maxcorner, Iterator_ First, Iterator_ Last ) {
Mincorner = VecType_( typename std::numeric_limits<VecType_::value_type>::max() );
Maxcorner = VecType_( typename std::numeric_limits<VecType_::value_type>::lowest() );
Mincorner = VecType_( std::numeric_limits<typename VecType_::value_type>::max() );
Maxcorner = VecType_( std::numeric_limits<typename VecType_::value_type>::lowest() );
std::for_each(
First, Last,