DEADSOFTWARE

Net: Buffer outgoing messages
[d2df-sdl.git] / src / game / g_net.pas
index c15df2bae9406b2cc043fce515bdc11b113b21da..83c8398991ff1ed0429f97212a1f80df69f55440 100644 (file)
@@ -22,7 +22,7 @@ uses
   e_log, e_msg, ENet, Classes, MAPDEF{$IFDEF USE_MINIUPNPC}, miniupnpc;{$ELSE};{$ENDIF}
 
 const
-  NET_PROTOCOL_VER = 174;
+  NET_PROTOCOL_VER = 181;
 
   NET_MAXCLIENTS = 24;
   NET_CHANS = 11;
@@ -48,6 +48,9 @@ const
 
   NET_EVERYONE = -1;
 
+  NET_UNRELIABLE = 0;
+  NET_RELIABLE = 1;
+
   NET_DISC_NONE: enet_uint32 = 0;
   NET_DISC_PROTOCOL: enet_uint32 = 1;
   NET_DISC_VERSION: enet_uint32 = 2;
@@ -63,6 +66,8 @@ const
   NET_STATE_AUTH = 1;
   NET_STATE_GAME = 2;
 
+  NET_CONNECT_TIMEOUT = 1000 * 10;
+
   BANLIST_FILENAME = 'banlist.txt';
   NETDUMP_FILENAME = 'netdump';
 
@@ -74,14 +79,15 @@ const
 
 type
   TNetClient = record
-    ID:      Byte;
-    Used:    Boolean;
-    State:   Byte;
-    Peer:    pENetPeer;
-    Player:  Word;
+    ID:       Byte;
+    Used:     Boolean;
+    State:    Byte;
+    Peer:     pENetPeer;
+    Player:   Word;
     RequestedFullUpdate: Boolean;
     RCONAuth: Boolean;
     Voted:    Boolean;
+    NetOut:   array [0..1] of TMsg;
   end;
   TBanRecord = record
     IP: LongWord;
@@ -124,6 +130,7 @@ var
   NetClientPort: Word   = 25666;
 
   NetIn, NetOut: TMsg;
+  NetBuf:        array [0..1] of TMsg;
 
   NetClients:     array of TNetClient;
   NetClientCount: Byte = 0;
@@ -206,7 +213,10 @@ implementation
 uses
   SysUtils,
   e_input, g_nethandler, g_netmsg, g_netmaster, g_player, g_window, g_console,
-  g_main, g_game, g_language, g_weapons, utils;
+  g_main, g_game, g_language, g_weapons, utils, ctypes;
+
+var
+  g_Net_DownloadTimeout: Single;
 
 
 { /// SERVICE FUNCTIONS /// }
@@ -270,6 +280,8 @@ var
 begin
   NetIn.Clear();
   NetOut.Clear();
+  NetBuf[NET_UNRELIABLE].Clear();
+  NetBuf[NET_RELIABLE].Clear();
   SetLength(NetClients, 0);
   NetPeer := nil;
   NetHost := nil;
@@ -296,14 +308,62 @@ begin
 end;
 
 procedure g_Net_Flush();
+var
+  T: Integer;
+  P: pENetPacket;
+  F, Chan: enet_uint32;
+  I: Integer;
 begin
-  enet_host_flush(NetHost);
+  F := 0;
+  Chan := NET_CHAN_GAME;
+
+  if NetMode = NET_SERVER then
+    for T := NET_UNRELIABLE to NET_RELIABLE do
+    begin
+      if NetBuf[T].CurSize > 0 then
+      begin
+        P := enet_packet_create(NetBuf[T].Data, NetBuf[T].CurSize, F);
+        if not Assigned(P) then continue;
+        enet_host_broadcast(NetHost, Chan, P);
+        NetBuf[T].Clear();
+      end;
+
+      for I := Low(NetClients) to High(NetClients) do
+      begin
+        if not NetClients[I].Used then continue;
+        if NetClients[I].NetOut[T].CurSize <= 0 then continue;
+        P := enet_packet_create(NetClients[I].NetOut[T].Data, NetClients[I].NetOut[T].CurSize, F);
+        if not Assigned(P) then continue;
+        enet_peer_send(NetClients[I].Peer, Chan, P);
+        NetClients[I].NetOut[T].Clear();
+      end;
+
+      // next and last iteration is always RELIABLE
+      F := LongWord(ENET_PACKET_FLAG_RELIABLE);
+      Chan := NET_CHAN_IMPORTANT;
+    end
+  else if NetMode = NET_CLIENT then
+    for T := NET_UNRELIABLE to NET_RELIABLE do
+    begin
+      if NetBuf[T].CurSize > 0 then
+      begin
+        P := enet_packet_create(NetBuf[T].Data, NetBuf[T].CurSize, F);
+        if not Assigned(P) then continue;
+        enet_peer_send(NetPeer, Chan, P);
+        NetBuf[T].Clear();
+      end;
+      // next and last iteration is always RELIABLE
+      F := LongWord(ENET_PACKET_FLAG_RELIABLE);
+      Chan := NET_CHAN_IMPORTANT;
+    end;
 end;
 
 procedure g_Net_Cleanup();
 begin
   NetIn.Clear();
   NetOut.Clear();
+  NetBuf[NET_UNRELIABLE].Clear();
+  NetBuf[NET_RELIABLE].Clear();
 
   SetLength(NetClients, 0);
   NetClientCount := 0;
@@ -408,6 +468,8 @@ begin
 
   NetMode := NET_SERVER;
   NetOut.Clear();
+  NetBuf[NET_UNRELIABLE].Clear();
+  NetBuf[NET_RELIABLE].Clear();
 
   if NetDump then
     g_Net_DumpStart();
@@ -436,6 +498,8 @@ begin
       enet_peer_reset(NetClients[I].Peer);
       NetClients[I].Peer := nil;
       NetClients[I].Used := False;
+      NetClients[I].NetOut[NET_UNRELIABLE].Free();
+      NetClients[I].NetOut[NET_RELIABLE].Free();
     end;
 
   if (NetMPeer <> nil) and (NetMHost <> nil) then g_Net_Slist_Disconnect;
@@ -454,34 +518,29 @@ end;
 
 procedure g_Net_Host_Send(ID: Integer; Reliable: Boolean; Chan: Byte = NET_CHAN_GAME);
 var
-  P: pENetPacket;
-  F: enet_uint32;
+  T: Integer;
 begin
   if (Reliable) then
-    F := LongWord(ENET_PACKET_FLAG_RELIABLE)
+    T := NET_RELIABLE
   else
-    F := 0;
+    T := NET_UNRELIABLE;
 
   if (ID >= 0) then
   begin
     if ID > High(NetClients) then Exit;
     if NetClients[ID].Peer = nil then Exit;
-
-    P := enet_packet_create(NetOut.Data, NetOut.CurSize, F);
-    if not Assigned(P) then Exit;
-
-    enet_peer_send(NetClients[ID].Peer, Chan, P);
+    // write size first
+    NetClients[ID].NetOut[T].Write(Integer(NetOut.CurSize));
+    NetClients[ID].NetOut[T].Write(NetOut);
   end
   else
   begin
-    P := enet_packet_create(NetOut.Data, NetOut.CurSize, F);
-    if not Assigned(P) then Exit;
-
-    enet_host_broadcast(NetHost, Chan, P);
+    // write size first
+    NetBuf[T].Write(Integer(NetOut.CurSize));
+    NetBuf[T].Write(NetOut);
   end;
 
   if NetDump then g_Net_DumpSendBuffer();
-  g_Net_Flush();
   NetOut.Clear();
 end;
 
@@ -582,6 +641,8 @@ begin
         Byte(NetClients[ID].Peer^.data^) := ID;
         NetClients[ID].State := NET_STATE_AUTH;
         NetClients[ID].RCONAuth := False;
+        NetClients[ID].NetOut[NET_UNRELIABLE].Alloc(NET_BUFSIZE*2);
+        NetClients[ID].NetOut[NET_RELIABLE].Alloc(NET_BUFSIZE*2);
 
         enet_peer_timeout(NetEvent.peer, ENET_PEER_TIMEOUT_LIMIT * 2, ENET_PEER_TIMEOUT_MINIMUM * 2, ENET_PEER_TIMEOUT_MAXIMUM * 2);
 
@@ -596,7 +657,7 @@ begin
         TC := @NetClients[ID];
 
         if NetDump then g_Net_DumpRecvBuffer(NetEvent.packet^.data, NetEvent.packet^.dataLength);
-        g_Net_HostMsgHandler(TC, NetEvent.packet);
+        g_Net_Host_HandlePacket(TC, NetEvent.packet, g_Net_HostMsgHandler);
       end;
 
       ENET_EVENT_TYPE_DISCONNECT:
@@ -624,6 +685,8 @@ begin
         TC^.Peer := nil;
         TC^.Player := 0;
         TC^.RequestedFullUpdate := False;
+        TC^.NetOut[NET_UNRELIABLE].Free();
+        TC^.NetOut[NET_RELIABLE].Free();
 
         FreeMemory(NetEvent.peer^.data);
         NetEvent.peer^.data := nil;
@@ -688,21 +751,20 @@ end;
 
 procedure g_Net_Client_Send(Reliable: Boolean; Chan: Byte = NET_CHAN_GAME);
 var
-  P: pENetPacket;
-  F: enet_uint32;
+  T: Integer;
 begin
   if (Reliable) then
-    F := LongWord(ENET_PACKET_FLAG_RELIABLE)
+    T := NET_RELIABLE
   else
-    F := 0;
+    T := NET_UNRELIABLE;
 
-  P := enet_packet_create(NetOut.Data, NetOut.CurSize, F);
-  if not Assigned(P) then Exit;
+  // write size first
+  NetBuf[T].Write(Integer(NetOut.CurSize));
+  NetBuf[T].Write(NetOut);
 
-  enet_peer_send(NetPeer, Chan, P);
   if NetDump then g_Net_DumpSendBuffer();
-  g_Net_Flush();
   NetOut.Clear();
+  g_Net_Flush(); // FIXME: for now, send immediately
 end;
 
 function g_Net_Client_Update(): enet_size_t;
@@ -714,7 +776,7 @@ begin
       ENET_EVENT_TYPE_RECEIVE:
       begin
         if NetDump then g_Net_DumpRecvBuffer(NetEvent.packet^.data, NetEvent.packet^.dataLength);
-        g_Net_ClientMsgHandler(NetEvent.packet);
+        g_Net_Client_HandlePacket(NetEvent.packet, g_Net_ClientMsgHandler);
       end;
 
       ENET_EVENT_TYPE_DISCONNECT:
@@ -736,7 +798,7 @@ begin
       ENET_EVENT_TYPE_RECEIVE:
       begin
         if NetDump then g_Net_DumpRecvBuffer(NetEvent.packet^.data, NetEvent.packet^.dataLength);
-        g_Net_ClientLightMsgHandler(NetEvent.packet);
+        g_Net_Client_HandlePacket(NetEvent.packet, g_Net_ClientLightMsgHandler);
       end;
 
       ENET_EVENT_TYPE_DISCONNECT:
@@ -753,6 +815,7 @@ end;
 function g_Net_Connect(IP: string; Port: enet_uint16): Boolean;
 var
   OuterLoop: Boolean;
+  TimeoutTime, T: Int64;
 begin
   if NetMode <> NET_NONE then
   begin
@@ -801,6 +864,9 @@ begin
     Exit;
   end;
 
+  // предупредить что ждем слишком долго через N секунд
+  TimeoutTime := GetTimer() + NET_CONNECT_TIMEOUT;
+
   OuterLoop := True;
   while OuterLoop do
   begin
@@ -820,13 +886,23 @@ begin
       end;
     end;
 
+    T := GetTimer();
+    if T > TimeoutTime then
+    begin
+      TimeoutTime := T + NET_CONNECT_TIMEOUT * 100; // одного предупреждения хватит
+      g_Console_Add(_lc[I_NET_MSG_TIMEOUT_WARN], True);
+      g_Console_Add(Format(_lc[I_NET_MSG_PORTS], [Integer(Port), Integer(NET_PING_PORT)]), True);
+    end;
+
     ProcessLoading(true);
 
-    if e_KeyPressed(IK_ESCAPE) or e_KeyPressed(IK_SPACE) or e_KeyPressed(VK_ESCAPE) then
+    if e_KeyPressed(IK_SPACE) or e_KeyPressed(IK_ESCAPE) or e_KeyPressed(VK_ESCAPE) or
+       e_KeyPressed(JOY0_JUMP) or e_KeyPressed(JOY1_JUMP) or e_KeyPressed(JOY2_JUMP) or e_KeyPressed(JOY3_JUMP) then
       OuterLoop := False;
   end;
 
   g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_TIMEOUT], True);
+  g_Console_Add(Format(_lc[I_NET_MSG_PORTS], [Integer(Port), Integer(NET_PING_PORT)]), True);
   if NetPeer <> nil then enet_peer_reset(NetPeer);
   if NetHost <> nil then
   begin
@@ -936,59 +1012,71 @@ begin
   enet_host_flush(NetHost);
 end;
 
+function UserRequestExit: Boolean;
+begin
+  Result := e_KeyPressed(IK_SPACE) or
+            e_KeyPressed(IK_ESCAPE) or
+            e_KeyPressed(VK_ESCAPE) or
+            e_KeyPressed(JOY0_JUMP) or
+            e_KeyPressed(JOY1_JUMP) or
+            e_KeyPressed(JOY2_JUMP) or
+            e_KeyPressed(JOY3_JUMP)
+end;
+
 function g_Net_Wait_Event(msgId: Word): TMemoryStream;
-var
-  downloadEvent: ENetEvent;
-  OuterLoop: Boolean;
-  MID: Byte;
-  Ptr: Pointer;
-  msgStream: TMemoryStream;
+  var
+    ev: ENetEvent;
+    rMsgId: Byte;
+    Ptr: Pointer;
+    stream: TMemoryStream;
+    status: cint;
 begin
-  FillChar(downloadEvent, SizeOf(downloadEvent), 0);
-  msgStream := nil;
-  OuterLoop := True;
-  while OuterLoop do
-  begin
-    while (enet_host_service(NetHost, @downloadEvent, 0) > 0) do
+  FillChar(ev, SizeOf(ev), 0);
+  stream := nil;
+  repeat
+    status := enet_host_service(NetHost, @ev, Trunc(g_Net_DownloadTimeout * 1000));
+    if status > 0 then
     begin
-      if (downloadEvent.kind = ENET_EVENT_TYPE_RECEIVE) then
-      begin
-        Ptr := downloadEvent.packet^.data;
-
-        MID := Byte(Ptr^);
-
-        if (MID = msgId) then
+      case ev.kind of
+        ENET_EVENT_TYPE_RECEIVE:
+          begin
+            Ptr := ev.packet^.data;
+            rMsgId := Byte(Ptr^);
+            if rMsgId = msgId then
+            begin
+              stream := TMemoryStream.Create;
+              stream.SetSize(ev.packet^.dataLength);
+              stream.WriteBuffer(Ptr^, ev.packet^.dataLength);
+              stream.Seek(0, soFromBeginning);
+              status := 1 (* received *)
+            end
+            else
+            begin
+              (* looks that game state always received, so ignore it *)
+              e_LogWritefln('g_Net_Wait_Event(%s): skip message %s', [msgId, rMsgId]);
+              status := 2 (* continue *)
+            end
+          end;
+      ENET_EVENT_TYPE_DISCONNECT:
         begin
-          msgStream := TMemoryStream.Create;
-          msgStream.SetSize(downloadEvent.packet^.dataLength);
-          msgStream.WriteBuffer(Ptr^, downloadEvent.packet^.dataLength);
-          msgStream.Seek(0, soFromBeginning);
-
-          OuterLoop := False;
-          enet_packet_destroy(downloadEvent.packet);
-          break;
-        end
-        else begin
-          enet_packet_destroy(downloadEvent.packet);
+          if (ev.data <= NET_DISC_MAX) then
+            g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' ' + _lc[TStrings_Locale(Cardinal(I_NET_DISC_NONE) + ev.data)], True);
+          status := -2 (* error: disconnected *)
         end;
-      end
       else
-        if (downloadEvent.kind = ENET_EVENT_TYPE_DISCONNECT) then
-        begin
-          if (downloadEvent.data <= NET_DISC_MAX) then
-            g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' ' +
-            _lc[TStrings_Locale(Cardinal(I_NET_DISC_NONE) + downloadEvent.data)], True);
-          OuterLoop := False;
-          Break;
-        end;
+        g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' unknown ENet event ' + IntToStr(Ord(ev.kind)), True);
+        status := -3 (* error: unknown event *)
+      end;
+      enet_packet_destroy(ev.packet)
+    end
+    else
+    begin
+      g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' timeout reached', True);
+      status := 0 (* error: timeout *)
     end;
-
     ProcessLoading(true);
-
-    if e_KeyPressed(IK_ESCAPE) or e_KeyPressed(IK_SPACE) or e_KeyPressed(VK_ESCAPE) then
-      break;
-  end;
-  Result := msgStream;
+  until (status <> 2) or UserRequestExit();
+  Result := stream
 end;
 
 function g_Net_IsHostBanned(IP: LongWord; Perm: Boolean = False): Boolean;
@@ -1255,13 +1343,15 @@ end;
 {$ENDIF}
 
 initialization
-
+  conRegVar('cl_downloadtimeout', @g_Net_DownloadTimeout, 0.0, 1000000.0, '', 'timeout in seconds, 0 to disable it');
+  g_Net_DownloadTimeout := 60;
   NetIn.Alloc(NET_BUFSIZE);
   NetOut.Alloc(NET_BUFSIZE);
-
+  NetBuf[NET_UNRELIABLE].Alloc(NET_BUFSIZE*2);
+  NetBuf[NET_RELIABLE].Alloc(NET_BUFSIZE*2);
 finalization
-
   NetIn.Free();
   NetOut.Free();
-
+  NetBuf[NET_UNRELIABLE].Free();
+  NetBuf[NET_RELIABLE].Free();
 end.