]> git.mxchange.org Git - flightgear.git/blobdiff - src/MultiPlayer/multiplaymgr.cxx
toggle fullscreen: also adapt GUI plane when resizing
[flightgear.git] / src / MultiPlayer / multiplaymgr.cxx
index 9a541382786f10de1cabd9433d103e07ba0e5de4..7d6ce8b33dc8909e2cb0655d88702ace460edaca 100644 (file)
@@ -34,7 +34,7 @@
 #include <iostream>
 #include <algorithm>
 #include <cstring>
-#include <osg/Math>             // isNaN
+#include <errno.h>
 
 #include <simgear/misc/stdint.hxx>
 #include <simgear/timing/timestamp.hxx>
 #include <simgear/props/props.hxx>
 
 #include <AIModel/AIManager.hxx>
+#include <AIModel/AIMultiplayer.hxx>
 #include <Main/fg_props.hxx>
 #include "multiplaymgr.hxx"
 #include "mpmessages.hxx"
+#include <FDM/flightProperties.hxx>
 
 using namespace std;
 
@@ -56,11 +58,18 @@ using namespace std;
 const char sMULTIPLAYMGR_BID[] = "$Id$";
 const char sMULTIPLAYMGR_HID[] = MULTIPLAYTXMGR_HID;
 
+struct IdPropertyList {
+  unsigned id;
+  const char* name;
+  simgear::props::Type type;
+};
+static const IdPropertyList* findProperty(unsigned id);
+  
 // A static map of protocol property id values to property paths,
 // This should be extendable dynamically for every specific aircraft ...
 // For now only that static list
-const FGMultiplayMgr::IdPropertyList
-FGMultiplayMgr::sIdPropertyList[] = {
+static const IdPropertyList sIdPropertyList[] = {
   {100, "surface-positions/left-aileron-pos-norm",  simgear::props::FLOAT},
   {101, "surface-positions/right-aileron-pos-norm", simgear::props::FLOAT},
   {102, "surface-positions/elevator-pos-norm",      simgear::props::FLOAT},
@@ -162,6 +171,8 @@ FGMultiplayMgr::sIdPropertyList[] = {
 
   {1300, "tanker", simgear::props::INT},
 
+  {1400, "scenery/events", simgear::props::STRING},
+
   {10001, "sim/multiplay/transmission-freq-hz",  simgear::props::STRING},
   {10002, "sim/multiplay/chat",  simgear::props::STRING},
 
@@ -229,34 +240,33 @@ FGMultiplayMgr::sIdPropertyList[] = {
   {10319, "sim/multiplay/generic/int[19]", simgear::props::INT}
 };
 
-const unsigned
-FGMultiplayMgr::numProperties = (sizeof(FGMultiplayMgr::sIdPropertyList)
-                                 / sizeof(FGMultiplayMgr::sIdPropertyList[0]));
+const unsigned int numProperties = (sizeof(sIdPropertyList)
+                                 / sizeof(sIdPropertyList[0]));
 
 // Look up a property ID using binary search.
 namespace
 {
   struct ComparePropertyId
   {
-    bool operator()(const FGMultiplayMgr::IdPropertyList& lhs,
-                    const FGMultiplayMgr::IdPropertyList& rhs)
+    bool operator()(const IdPropertyList& lhs,
+                    const IdPropertyList& rhs)
     {
       return lhs.id < rhs.id;
     }
-    bool operator()(const FGMultiplayMgr::IdPropertyList& lhs,
+    bool operator()(const IdPropertyList& lhs,
                     unsigned id)
     {
       return lhs.id < id;
     }
     bool operator()(unsigned id,
-                    const FGMultiplayMgr::IdPropertyList& rhs)
+                    const IdPropertyList& rhs)
     {
       return id < rhs.id;
     }
-  };
-    
+  };    
 }
-const FGMultiplayMgr::IdPropertyList* FGMultiplayMgr::findProperty(unsigned id)
+
+const IdPropertyList* findProperty(unsigned id)
 {
   std::pair<const IdPropertyList*, const IdPropertyList*> result
     = std::equal_range(sIdPropertyList, sIdPropertyList + numProperties, id,
@@ -276,8 +286,7 @@ namespace
     const xdr_data_t* xdr = data;
     while (xdr < end) {
       unsigned id = XDR_decode_uint32(*xdr);
-      const FGMultiplayMgr::IdPropertyList* plist
-        = FGMultiplayMgr::findProperty(id);
+      const IdPropertyList* plist = findProperty(id);
     
       if (plist) {
         xdr++;
@@ -292,7 +301,7 @@ namespace
         case props::DOUBLE:
           {
             float val = XDR_decode_float(*xdr);
-            if (osg::isNaN(val))
+            if (SGMisc<float>::isNaN(val))
               return false;
             xdr++;
             break;
@@ -336,6 +345,24 @@ namespace
     return true;
   }
 }
+
+class MPPropertyListener : public SGPropertyChangeListener
+{
+public:
+  MPPropertyListener(FGMultiplayMgr* mp) :
+    _multiplay(mp)
+  {
+  }
+
+  virtual void childAdded(SGPropertyNode*, SGPropertyNode*)
+  {
+    _multiplay->setPropertiesChanged();
+  }
+
+private:
+  FGMultiplayMgr* _multiplay;
+};
+
 //////////////////////////////////////////////////////////////////////
 //
 //  MultiplayMgr constructor
@@ -343,9 +370,9 @@ namespace
 //////////////////////////////////////////////////////////////////////
 FGMultiplayMgr::FGMultiplayMgr() 
 {
-  mSocket        = 0;
   mInitialised   = false;
   mHaveServer    = false;
+  mListener = NULL;
 } // FGMultiplayMgr::FGMultiplayMgr()
 //////////////////////////////////////////////////////////////////////
 
@@ -356,7 +383,7 @@ FGMultiplayMgr::FGMultiplayMgr()
 //////////////////////////////////////////////////////////////////////
 FGMultiplayMgr::~FGMultiplayMgr() 
 {
-  Close();
+  
 } // FGMultiplayMgr::~FGMultiplayMgr()
 //////////////////////////////////////////////////////////////////////
 
@@ -375,30 +402,50 @@ FGMultiplayMgr::init (void)
     SG_LOG(SG_NETWORK, SG_WARN, "FGMultiplayMgr::init - already initialised");
     return;
   }
+
+  SGPropertyNode* propOnline = fgGetNode("/sim/multiplay/online", true);
+  propOnline->setBoolValue(false);
+  propOnline->setAttribute(SGPropertyNode::PRESERVE, true);
+
   //////////////////////////////////////////////////
   //  Set members from property values
   //////////////////////////////////////////////////
   short rxPort = fgGetInt("/sim/multiplay/rxport");
   string rxAddress = fgGetString("/sim/multiplay/rxhost");
-  short txPort = fgGetInt("/sim/multiplay/txport");
+  short txPort = fgGetInt("/sim/multiplay/txport", 5000);
   string txAddress = fgGetString("/sim/multiplay/txhost");
+  
+  int hz = fgGetInt("/sim/multiplay/tx-rate-hz", 10);
+  if (hz < 1) {
+    hz = 10;
+  }
+  
+  mDt = 1.0 / hz;
+  mTimeUntilSend = 0.0;
+  
   mCallsign = fgGetString("/sim/multiplay/callsign");
-  if (txPort > 0 && !txAddress.empty()) {
+  if ((!txAddress.empty()) && (txAddress!="0")) {
     mServer.set(txAddress.c_str(), txPort);
     if (strncmp (mServer.getHost(), "0.0.0.0", 8) == 0) {
       mHaveServer = false;
-      SG_LOG(SG_NETWORK, SG_DEBUG,
-        "FGMultiplayMgr - could not resolve '"
-        << txAddress << "', Multiplayermode disabled");
+      SG_LOG(SG_NETWORK, SG_ALERT,
+        "Cannot enable multiplayer mode: resolving MP server address '"
+        << txAddress << "' failed.");
+      return;
     } else {
+      SG_LOG(SG_NETWORK, SG_INFO, "FGMultiplayMgr - have server");
       mHaveServer = true;
     }
     if (rxPort <= 0)
       rxPort = txPort;
+  } else {
+    SG_LOG(SG_NETWORK, SG_INFO, "FGMultiplayMgr - multiplayer mode disabled (no MP server specificed).");
+    return;
   }
+
   if (rxPort <= 0) {
-    SG_LOG(SG_NETWORK, SG_DEBUG,
-      "FGMultiplayMgr - No receiver port, Multiplayermode disabled");
+    SG_LOG(SG_NETWORK, SG_ALERT,
+      "Cannot enable multiplayer mode: No receiver port specified.");
     return;
   }
   if (mCallsign.empty())
@@ -408,23 +455,35 @@ FGMultiplayMgr::init (void)
   SG_LOG(SG_NETWORK,SG_INFO,"FGMultiplayMgr::init-rxaddress="<<rxAddress );
   SG_LOG(SG_NETWORK,SG_INFO,"FGMultiplayMgr::init-rxport= "<<rxPort);
   SG_LOG(SG_NETWORK,SG_INFO,"FGMultiplayMgr::init-callsign= "<<mCallsign);
-  Close(); // Should Init be called twice, close Socket first
-           // A memory leak was reported here by valgrind
-  mSocket = new simgear::Socket();
+  
+  mSocket.reset(new simgear::Socket());
   if (!mSocket->open(false)) {
-    SG_LOG( SG_NETWORK, SG_DEBUG,
-            "FGMultiplayMgr::init - Failed to create data socket" );
+    SG_LOG( SG_NETWORK, SG_ALERT,
+            "Cannot enable multiplayer mode: creating data socket failed." );
     return;
   }
   mSocket->setBlocking(false);
   if (mSocket->bind(rxAddress.c_str(), rxPort) != 0) {
-    perror("bind");
-    SG_LOG( SG_NETWORK, SG_DEBUG,
-            "FGMultiplayMgr::Open - Failed to bind receive socket" );
+    SG_LOG( SG_NETWORK, SG_ALERT,
+            "Cannot enable multiplayer mode: binding receive socket failed. "
+            << strerror(errno) << "(errno " << errno << ")");
     return;
   }
   
+  mPropertiesChanged = true;
+  mListener = new MPPropertyListener(this);
+  globals->get_props()->addChangeListener(mListener, false);
+  
+  fgSetBool("/sim/multiplay/online", true);
   mInitialised = true;
+
+  SG_LOG(SG_NETWORK, SG_ALERT, "Multiplayer mode active!");
+
+  if (!fgGetBool("/sim/ai/enabled"))
+  {
+      // multiplayer depends on AI module
+      fgSetBool("/sim/ai/enabled", true);
+  }
 } // FGMultiplayMgr::init()
 //////////////////////////////////////////////////////////////////////
 
@@ -435,19 +494,39 @@ FGMultiplayMgr::init (void)
 //
 //////////////////////////////////////////////////////////////////////
 void
-FGMultiplayMgr::Close (void) 
+FGMultiplayMgr::shutdown (void) 
 {
-  mMultiPlayerMap.clear();
-
-  if (mSocket) {
+  fgSetBool("/sim/multiplay/online", false);
+  
+  if (mSocket.get()) {
     mSocket->close();
-    delete mSocket;
-    mSocket = 0;
+    mSocket.reset(); 
+  }
+  
+  MultiPlayerMap::iterator it = mMultiPlayerMap.begin(),
+    end = mMultiPlayerMap.end();
+  for (; it != end; ++it) {
+    it->second->setDie(true);
   }
+  mMultiPlayerMap.clear();
+  
+  if (mListener) {
+    globals->get_props()->removeChangeListener(mListener);
+    delete mListener;
+    mListener = NULL;
+  }
+  
   mInitialised = false;
 } // FGMultiplayMgr::Close(void)
 //////////////////////////////////////////////////////////////////////
 
+void
+FGMultiplayMgr::reinit()
+{
+  shutdown();
+  init();
+}
+
 //////////////////////////////////////////////////////////////////////
 //
 //  Description: Sends the position data for the local position.
@@ -531,8 +610,8 @@ FGMultiplayMgr::isSane(const FGExternalMotionData& motionInfo)
 {
     // check for corrupted data (NaNs)
     bool isCorrupted = false;
-    isCorrupted |= ((osg::isNaN(motionInfo.time           )) ||
-                    (osg::isNaN(motionInfo.lag            )) ||
+    isCorrupted |= ((SGMisc<double>::isNaN(motionInfo.time           )) ||
+                    (SGMisc<double>::isNaN(motionInfo.lag            )) ||
                     (osg::isNaN(motionInfo.orientation(3) )));
     for (unsigned i = 0; (i < 3)&&(!isCorrupted); ++i)
     {
@@ -572,7 +651,8 @@ FGMultiplayMgr::SendMyPosition(const FGExternalMotionData& motionInfo)
 
   strncpy(PosMsg->Model, fgGetString("/sim/model/path"), MAX_MODEL_NAME_LEN);
   PosMsg->Model[MAX_MODEL_NAME_LEN - 1] = '\0';
-  if (fgGetBool("/sim/freeze/replay-state", true))
+  if (fgGetBool("/sim/freeze/replay-state", true)&&
+      fgGetBool("/sim/multiplay/freeze-on-replay",true))
   {
       // do not send position updates during replay
       for (unsigned i = 0 ; i < 3; ++i)
@@ -757,7 +837,7 @@ FGMultiplayMgr::SendTextMessage(const string &MsgText)
 //  
 //////////////////////////////////////////////////////////////////////
 void
-FGMultiplayMgr::update(double) 
+FGMultiplayMgr::update(double dt
 {
   if (!mInitialised)
     return;
@@ -765,6 +845,14 @@ FGMultiplayMgr::update(double)
   /// Just for expiry
   long stamp = SGTimeStamp::now().getSeconds();
 
+  //////////////////////////////////////////////////
+  //  Send if required
+  //////////////////////////////////////////////////
+  mTimeUntilSend -= dt;
+  if (mTimeUntilSend <= 0.0) {
+    Send();
+  }
+
   //////////////////////////////////////////////////
   //  Read the receive socket and process any data
   //////////////////////////////////////////////////
@@ -778,16 +866,33 @@ FGMultiplayMgr::update(double)
     //  packet waiting to be processed.
     //////////////////////////////////////////////////
     simgear::IPAddress SenderAddress;
-    bytes = mSocket->recvfrom(msgBuf.Msg, sizeof(msgBuf.Msg), 0,
+    int RecvStatus = mSocket->recvfrom(msgBuf.Msg, sizeof(msgBuf.Msg), 0,
                               &SenderAddress);
     //////////////////////////////////////////////////
     //  no Data received
     //////////////////////////////////////////////////
-    if (bytes <= 0) {
-      if (errno != EAGAIN && errno != 0) // MSVC output "NoError" otherwise
-        perror("FGMultiplayMgr::MP_ProcessData");
-      break;
+    if (RecvStatus == 0)
+        break;
+
+    // socket error reported?
+    // errno isn't thread-safe - so only check its value when
+    // socket return status < 0 really indicates a failure.
+    if ((RecvStatus < 0)&&
+        ((errno == EAGAIN) || (errno == 0))) // MSVC output "NoError" otherwise
+    {
+        // ignore "normal" errors
+        break;
+    }
+
+    if (RecvStatus<0)
+    {
+        SG_LOG(SG_NETWORK, SG_DEBUG, "FGMultiplayMgr::MP_ProcessData - Unable to receive data. "
+               << strerror(errno) << "(errno " << errno << ")");
+        break;
     }
+
+    // status is positive: bytes received
+    bytes = (ssize_t) RecvStatus;
     if (bytes <= static_cast<ssize_t>(sizeof(T_MsgHdr))) {
       SG_LOG( SG_NETWORK, SG_DEBUG, "FGMultiplayMgr::MP_ProcessData - "
               << "received message with insufficient data" );
@@ -813,7 +918,7 @@ FGMultiplayMgr::update(double)
               << "message has invalid protocol number!" );
       break;
     }
-    if (MsgHdr->MsgLen != bytes) {
+    if (static_cast<ssize_t>(MsgHdr->MsgLen) != bytes) {
       SG_LOG(SG_NETWORK, SG_DEBUG, "FGMultiplayMgr::MP_ProcessData - "
              << "message from " << MsgHdr->Callsign << " has invalid length!");
       break;
@@ -854,6 +959,140 @@ FGMultiplayMgr::update(double)
 } // FGMultiplayMgr::ProcessData(void)
 //////////////////////////////////////////////////////////////////////
 
+void
+FGMultiplayMgr::Send()
+{
+  using namespace simgear;
+  
+  findProperties();
+    
+  // smooth the send rate, by adjusting based on the 'remainder' time, which
+  // is how -ve mTimeUntilSend is. Watch for large values and ignore them,
+  // however.
+    if ((mTimeUntilSend < 0.0) && (fabs(mTimeUntilSend) < mDt)) {
+      mTimeUntilSend = mDt + mTimeUntilSend;
+    } else {
+      mTimeUntilSend = mDt;
+    }
+
+    double sim_time = globals->get_sim_time_sec();
+//    static double lastTime = 0.0;
+    
+   // SG_LOG(SG_GENERAL, SG_INFO, "actual dt=" << sim_time - lastTime);
+//    lastTime = sim_time;
+    
+    FlightProperties ifce;
+
+    // put together a motion info struct, you will get that later
+    // from FGInterface directly ...
+    FGExternalMotionData motionInfo;
+
+    // The current simulation time we need to update for,
+    // note that the simulation time is updated before calling all the
+    // update methods. Thus it contains the time intervals *end* time.
+    // The FDM is already run, so the states belong to that time.
+    motionInfo.time = sim_time;
+    motionInfo.lag = mDt;
+
+    // These are for now converted from lat/lon/alt and euler angles.
+    // But this should change in FGInterface ...
+    double lon = ifce.get_Longitude();
+    double lat = ifce.get_Latitude();
+    // first the aprioriate structure for the geodetic one
+    SGGeod geod = SGGeod::fromRadFt(lon, lat, ifce.get_Altitude());
+    // Convert to cartesion coordinate
+    motionInfo.position = SGVec3d::fromGeod(geod);
+    
+    // The quaternion rotating from the earth centered frame to the
+    // horizontal local frame
+    SGQuatf qEc2Hl = SGQuatf::fromLonLatRad((float)lon, (float)lat);
+    // The orientation wrt the horizontal local frame
+    float heading = ifce.get_Psi();
+    float pitch = ifce.get_Theta();
+    float roll = ifce.get_Phi();
+    SGQuatf hlOr = SGQuatf::fromYawPitchRoll(heading, pitch, roll);
+    // The orientation of the vehicle wrt the earth centered frame
+    motionInfo.orientation = qEc2Hl*hlOr;
+
+    if (!globals->get_subsystem("flight")->is_suspended()) {
+      // velocities
+      motionInfo.linearVel = SG_FEET_TO_METER*SGVec3f(ifce.get_uBody(),
+                                                      ifce.get_vBody(),
+                                                      ifce.get_wBody());
+      motionInfo.angularVel = SGVec3f(ifce.get_P_body(),
+                                      ifce.get_Q_body(),
+                                      ifce.get_R_body());
+      
+      // accels, set that to zero for now.
+      // Angular accelerations are missing from the interface anyway,
+      // linear accelerations are screwed up at least for JSBSim.
+//  motionInfo.linearAccel = SG_FEET_TO_METER*SGVec3f(ifce.get_U_dot_body(),
+//                                                    ifce.get_V_dot_body(),
+//                                                    ifce.get_W_dot_body());
+      motionInfo.linearAccel = SGVec3f::zeros();
+      motionInfo.angularAccel = SGVec3f::zeros();
+    } else {
+      // if the interface is suspendend, prevent the client from
+      // wild extrapolations
+      motionInfo.linearVel = SGVec3f::zeros();
+      motionInfo.angularVel = SGVec3f::zeros();
+      motionInfo.linearAccel = SGVec3f::zeros();
+      motionInfo.angularAccel = SGVec3f::zeros();
+    }
+
+    // now send the properties
+    PropertyMap::iterator it;
+    for (it = mPropertyMap.begin(); it != mPropertyMap.end(); ++it) {
+      FGPropertyData* pData = new FGPropertyData;
+      pData->id = it->first;
+      pData->type = it->second->getType();
+      
+      switch (pData->type) {
+        case props::INT:
+        case props::LONG:
+        case props::BOOL:
+          pData->int_value = it->second->getIntValue();
+          break;
+        case props::FLOAT:
+        case props::DOUBLE:
+          pData->float_value = it->second->getFloatValue();
+          break;
+        case props::STRING:
+        case props::UNSPECIFIED:
+          {
+            // FIXME: We assume unspecified are strings for the moment.
+
+            const char* cstr = it->second->getStringValue();
+            int len = strlen(cstr);
+            
+            if (len > 0)
+            {            
+              pData->string_value = new char[len + 1];
+              strcpy(pData->string_value, cstr);
+            }
+            else
+            {
+              // Size 0 - ignore
+              pData->string_value = 0;            
+            }
+
+            //cout << " Sending property " << pData->id << " " << pData->type << " " <<  pData->string_value << "\n";
+            break;        
+          }
+        default:
+          // FIXME Currently default to a float. 
+          //cout << "Unknown type when iterating through props: " << pData->type << "\n";
+          pData->float_value = it->second->getFloatValue();
+          break;
+      }
+      
+      motionInfo.properties.push_back(pData);
+    }
+
+    SendMyPosition(motionInfo);
+}
+
+
 //////////////////////////////////////////////////////////////////////
 //
 //  handle a position message
@@ -1000,7 +1239,7 @@ FGMultiplayMgr::ProcessPosMsg(const FGMultiplayMgr::MsgBuf& Msg,
     else
     {
       // We failed to find the property. We'll try the next packet immediately.
-      SG_LOG(SG_NETWORK, SG_INFO, "FGMultiplayMgr::ProcessPosMsg - "
+      SG_LOG(SG_NETWORK, SG_DEBUG, "FGMultiplayMgr::ProcessPosMsg - "
              "message from " << MsgHdr->Callsign << " has unknown property id "
              << id); 
     }
@@ -1081,7 +1320,7 @@ FGMultiplayMgr::addMultiplayer(const std::string& callsign,
   mp->setCallSign(callsign);
   mMultiPlayerMap[callsign] = mp;
 
-  FGAIManager *aiMgr = (FGAIManager*)globals->get_subsystem("ai_model");
+  FGAIManager *aiMgr = (FGAIManager*)globals->get_subsystem("ai-model");
   if (aiMgr) {
     aiMgr->attach(mp);
 
@@ -1101,3 +1340,29 @@ FGMultiplayMgr::getMultiplayer(const std::string& callsign)
   else
     return 0;
 }
+
+void
+FGMultiplayMgr::findProperties()
+{
+  if (!mPropertiesChanged) {
+    return;
+  }
+  
+  mPropertiesChanged = false;
+  
+  for (unsigned i = 0; i < numProperties; ++i) {
+      const char* name = sIdPropertyList[i].name;
+      SGPropertyNode* pNode = globals->get_props()->getNode(name);
+      if (!pNode) {
+        continue;
+      }
+      
+      int id = sIdPropertyList[i].id;
+      if (mPropertyMap.find(id) != mPropertyMap.end()) {
+        continue; // already activated
+      }
+      
+      mPropertyMap[id] = pNode;
+      SG_LOG(SG_NETWORK, SG_DEBUG, "activating MP property:" << pNode->getPath());
+    }
+}