Exemple #1
0
        /// <summary>
        /// Processes all inbound GameAction messages.<para />
        /// Dispatches all outgoing messages.<para />
        /// Removes dead sessions.
        /// </summary>
        public static int DoSessionWork()
        {
            int sessionCount;

            sessionLock.EnterUpgradeableReadLock();
            try
            {
                sessionCount = sessions.Count;

                // The session tick inbound processes all inbound GameAction messages
                ServerPerformanceMonitor.RegisterEventStart(ServerPerformanceMonitor.MonitorType.DoSessionWork_TickInbound);
                foreach (var s in sessions)
                {
                    s.TickInbound();
                }
                ServerPerformanceMonitor.RegisterEventEnd(ServerPerformanceMonitor.MonitorType.DoSessionWork_TickInbound);

                // Do not combine the above and below loops. All inbound messages should be processed first and then all outbound messages should be processed second.

                // The session tick outbound processes pending actions and handles outgoing messages
                ServerPerformanceMonitor.RegisterEventStart(ServerPerformanceMonitor.MonitorType.DoSessionWork_TickOutbound);
                foreach (var s in sessions)
                {
                    s.TickOutbound();
                }
                ServerPerformanceMonitor.RegisterEventEnd(ServerPerformanceMonitor.MonitorType.DoSessionWork_TickOutbound);

                // Removes sessions in the NetworkTimeout state, including sessions that have reached a timeout limit.
                ServerPerformanceMonitor.RegisterEventStart(ServerPerformanceMonitor.MonitorType.DoSessionWork_RemoveSessions);
                for (int i = sessions.Count - 1; i >= 0; i--)
                {
                    var sesh = sessions[i];
                    switch (sesh.State)
                    {
                    case SessionState.NetworkTimeout:
                        sesh.DropSession(string.IsNullOrEmpty(sesh.BootSessionReason) ? "Network Timeout" : sesh.BootSessionReason);
                        break;

                    case SessionState.ClientConnectionFailure:
                        // needs to send the client the "git outa here" message or client will zombie out and appear to the player like it's still in game.
                        // TO-DO: see if PacketHeaderFlags.NetErrorDisconnect will work for this
                        sesh.BootSession("Client connection failure", new GameMessageBootAccount(sesh));
                        break;

                    case SessionState.ClientSentNetErrorDisconnect:
                        sesh.DropSession(string.IsNullOrEmpty(sesh.BootSessionReason) ? "client sent network error disconnect" : sesh.BootSessionReason);
                        break;
                    }
                }
                ServerPerformanceMonitor.RegisterEventEnd(ServerPerformanceMonitor.MonitorType.DoSessionWork_RemoveSessions);
            }
            finally
            {
                sessionLock.ExitUpgradeableReadLock();
            }
            return(sessionCount);
        }
Exemple #2
0
        /// <summary>
        /// Projected to run at a reasonable rate for gameplay (30-60fps)
        /// </summary>
        public static bool UpdateGameWorld()
        {
            if (updateGameWorldRateLimiter.GetSecondsToWaitBeforeNextEvent() > 0)
            {
                return(false);
            }

            updateGameWorldRateLimiter.RegisterEvent();

            ServerPerformanceMonitor.RegisterEventStart(ServerPerformanceMonitor.MonitorType.UpdateGameWorld_Entire);

            // update positions through physics engine
            ServerPerformanceMonitor.RegisterEventStart(ServerPerformanceMonitor.MonitorType.UpdateGameWorld_HandlePhysics);
            var movedObjects = HandlePhysics(Timers.PortalYearTicks);

            ServerPerformanceMonitor.RegisterEventEnd(ServerPerformanceMonitor.MonitorType.UpdateGameWorld_HandlePhysics);

            // iterate through objects that have changed landblocks
            ServerPerformanceMonitor.RegisterEventStart(ServerPerformanceMonitor.MonitorType.UpdateGameWorld_RelocateObjectForPhysics);
            foreach (var movedObject in movedObjects)
            {
                // NOTE: The object's Location can now be null, if a player logs out, or an item is picked up
                if (movedObject.Location == null)
                {
                    continue;
                }

                // assume adjacency move here?
                LandblockManager.RelocateObjectForPhysics(movedObject, true);
            }
            ServerPerformanceMonitor.RegisterEventEnd(ServerPerformanceMonitor.MonitorType.UpdateGameWorld_RelocateObjectForPhysics);

            // Tick all of our Landblocks and WorldObjects
            ServerPerformanceMonitor.RegisterEventStart(ServerPerformanceMonitor.MonitorType.UpdateGameWorld_landblock_Tick);
            var loadedLandblocks = LandblockManager.GetLoadedLandblocks();

            foreach (var landblock in loadedLandblocks)
            {
                landblock.Tick(Time.GetUnixTime());
            }
            ServerPerformanceMonitor.RegisterEventEnd(ServerPerformanceMonitor.MonitorType.UpdateGameWorld_landblock_Tick);

            // clean up inactive landblocks
            LandblockManager.UnloadLandblocks();

            HouseManager.Tick();

            ServerPerformanceMonitor.RegisterEventEnd(ServerPerformanceMonitor.MonitorType.UpdateGameWorld_Entire);

            return(true);
        }
Exemple #3
0
        public static void Tick(double portalYearTicks)
        {
            // update positions through physics engine
            ServerPerformanceMonitor.RestartEvent(ServerPerformanceMonitor.MonitorType.LandblockManager_TickPhysics);
            TickPhysics(portalYearTicks);
            ServerPerformanceMonitor.RegisterEventEnd(ServerPerformanceMonitor.MonitorType.LandblockManager_TickPhysics);

            // Tick all of our Landblocks and WorldObjects
            ServerPerformanceMonitor.RestartEvent(ServerPerformanceMonitor.MonitorType.LandblockManager_Tick);
            Tick();
            ServerPerformanceMonitor.RegisterEventEnd(ServerPerformanceMonitor.MonitorType.LandblockManager_Tick);

            // clean up inactive landblocks
            UnloadLandblocks();
        }
Exemple #4
0
        /// <summary>
        /// Projected to run at a reasonable rate for gameplay (30-60fps)
        /// </summary>
        public static bool UpdateGameWorld()
        {
            if (updateGameWorldRateLimiter.GetSecondsToWaitBeforeNextEvent() > 0)
            {
                return(false);
            }

            updateGameWorldRateLimiter.RegisterEvent();

            ServerPerformanceMonitor.RegisterEventStart(ServerPerformanceMonitor.MonitorType.UpdateGameWorld_Entire);

            LandblockManager.Tick(Timers.PortalYearTicks);

            HouseManager.Tick();

            ServerPerformanceMonitor.RegisterEventEnd(ServerPerformanceMonitor.MonitorType.UpdateGameWorld_Entire);

            return(true);
        }
Exemple #5
0
        public static void Tick(double portalYearTicks)
        {
            // update positions through physics engine
            ServerPerformanceMonitor.RestartEvent(ServerPerformanceMonitor.MonitorType.LandblockManager_TickPhysics);
            TickPhysics(portalYearTicks);
            ServerPerformanceMonitor.RegisterEventEnd(ServerPerformanceMonitor.MonitorType.LandblockManager_TickPhysics);

            // Tick all of our Landblocks and WorldObjects (Work that can be multi-threaded)
            ServerPerformanceMonitor.RestartEvent(ServerPerformanceMonitor.MonitorType.LandblockManager_TickMultiThreadedWork);
            TickMultiThreadedWork();
            ServerPerformanceMonitor.RegisterEventEnd(ServerPerformanceMonitor.MonitorType.LandblockManager_TickMultiThreadedWork);

            // Tick all of our Landblocks and WorldObjects (Work that must be single threaded)
            ServerPerformanceMonitor.RestartEvent(ServerPerformanceMonitor.MonitorType.LandblockManager_TickSingleThreadedWork);
            TickSingleThreadedWork();
            ServerPerformanceMonitor.RegisterEventEnd(ServerPerformanceMonitor.MonitorType.LandblockManager_TickSingleThreadedWork);

            // clean up inactive landblocks
            UnloadLandblocks();
        }
Exemple #6
0
        /// <summary>
        /// Processes all inbound GameAction messages.<para />
        /// Dispatches all outgoing messages.<para />
        /// Removes dead sessions.
        /// </summary>
        public static int DoSessionWork()
        {
            int sessionCount = 0;

            sessionLock.EnterUpgradeableReadLock();
            try
            {
                // The session tick outbound processes pending actions and handles outgoing messages
                ServerPerformanceMonitor.RegisterEventStart(ServerPerformanceMonitor.MonitorType.DoSessionWork_TickOutbound);
                foreach (var s in sessionMap)
                {
                    s?.TickOutbound();
                }
                ServerPerformanceMonitor.RegisterEventEnd(ServerPerformanceMonitor.MonitorType.DoSessionWork_TickOutbound);

                // Removes sessions in the NetworkTimeout state, including sessions that have reached a timeout limit.
                ServerPerformanceMonitor.RegisterEventStart(ServerPerformanceMonitor.MonitorType.DoSessionWork_RemoveSessions);
                foreach (var session in sessionMap.Where(k => !Equals(null, k)))
                {
                    var pendingTerm = session.PendingTermination;
                    if (session.PendingTermination != null && session.PendingTermination.TerminationStatus == SessionTerminationPhase.SessionWorkCompleted)
                    {
                        session.DropSession();
                        session.PendingTermination.TerminationStatus = SessionTerminationPhase.WorldManagerWorkCompleted;
                    }

                    sessionCount++;
                }
                ServerPerformanceMonitor.RegisterEventEnd(ServerPerformanceMonitor.MonitorType.DoSessionWork_RemoveSessions);
            }
            finally
            {
                sessionLock.ExitUpgradeableReadLock();
            }
            return(sessionCount);
        }
Exemple #7
0
        /// <summary>
        /// Manages updating all entities on the world.
        ///  - Server-side command-line commands are handled in their own thread.
        ///  - Database I/O is handled in its own thread.
        ///  - Network commands come from their own listener threads, and are queued for each sessions which are then processed here.
        ///  - This thread does the rest of the work!
        /// </summary>
        private static void UpdateWorld()
        {
            log.DebugFormat("Starting UpdateWorld thread");

            WorldActive = true;
            var worldTickTimer = new Stopwatch();

            while (!pendingWorldStop)
            {
                /*
                 * When it comes to thread safety for Landblocks and WorldObjects, ACE makes the following assumptions:
                 *
                 * Inbound ClientMessages and GameActions are handled on the main UpdateWorld thread.
                 * - These actions may load Landblocks and modify other WorldObjects safely.
                 *
                 * PlayerEnterWorld queue is run on the main UpdateWorld thread.
                 * - These actions may load Landblocks and modify other WorldObjects safely.
                 *
                 * Landblock Groups (calculated by LandblockManager) can be processed in parallel.
                 *
                 * Adjacent Landblocks will always be run on the same thread.
                 *
                 * Non-adjacent landblocks might be run on different threads.
                 * - If two non-adjacent landblocks both touch the same landblock, and that landblock is active, they will be run on the same thread.
                 *
                 * Database results are returned from a task spawned in SerializedShardDatabase (via callback).
                 * - Minimal processing should be done from the callback. Return as quickly as possible to let the database thread do database work.
                 * - The processing of these results should be queued to an ActionQueue
                 *
                 * The only cases where it's acceptable for to create a new Task, Thread or Parallel loop are the following:
                 * - Every scenario must be one where you don't care about breaking ACE
                 * - DeveloperCommand Handlers
                 */

                worldTickTimer.Restart();

                ServerPerformanceMonitor.RestartEvent(ServerPerformanceMonitor.MonitorType.PlayerManager_Tick);
                PlayerManager.Tick();
                ServerPerformanceMonitor.RegisterEventEnd(ServerPerformanceMonitor.MonitorType.PlayerManager_Tick);

                ServerPerformanceMonitor.RestartEvent(ServerPerformanceMonitor.MonitorType.NetworkManager_InboundClientMessageQueueRun);
                NetworkManager.InboundMessageQueue.RunActions();
                ServerPerformanceMonitor.RegisterEventEnd(ServerPerformanceMonitor.MonitorType.NetworkManager_InboundClientMessageQueueRun);

                // This will consist of PlayerEnterWorld actions, as well as other game world actions that require thread safety
                ServerPerformanceMonitor.RestartEvent(ServerPerformanceMonitor.MonitorType.actionQueue_RunActions);
                ActionQueue.RunActions();
                ServerPerformanceMonitor.RegisterEventEnd(ServerPerformanceMonitor.MonitorType.actionQueue_RunActions);

                ServerPerformanceMonitor.RestartEvent(ServerPerformanceMonitor.MonitorType.DelayManager_RunActions);
                DelayManager.RunActions();
                ServerPerformanceMonitor.RegisterEventEnd(ServerPerformanceMonitor.MonitorType.DelayManager_RunActions);

                ServerPerformanceMonitor.RestartEvent(ServerPerformanceMonitor.MonitorType.UpdateGameWorld);
                var gameWorldUpdated = UpdateGameWorld();
                ServerPerformanceMonitor.RegisterEventEnd(ServerPerformanceMonitor.MonitorType.UpdateGameWorld);

                ServerPerformanceMonitor.RestartEvent(ServerPerformanceMonitor.MonitorType.NetworkManager_DoSessionWork);
                int sessionCount = NetworkManager.DoSessionWork();
                ServerPerformanceMonitor.RegisterEventEnd(ServerPerformanceMonitor.MonitorType.NetworkManager_DoSessionWork);

                ServerPerformanceMonitor.Tick();

                // We only relax the CPU if our game world is able to update at the target rate.
                // We do not sleep if our game world just updated. This is to prevent the scenario where our game world can't keep up. We don't want to add further delays.
                // If our game world is able to keep up, it will not be updated on most ticks. It's on those ticks (between updates) that we will relax the CPU.
                if (!gameWorldUpdated)
                {
                    Thread.Sleep(sessionCount == 0 ? 10 : 1); // Relax the CPU more if no sessions are connected
                }
                Timers.PortalYearTicks += worldTickTimer.Elapsed.TotalSeconds;
            }

            // World has finished operations and concedes the thread to garbage collection
            WorldActive = false;
        }
Exemple #8
0
        public static void ProcessPacket(ClientPacket packet, IPEndPoint endPoint, IPEndPoint listenerEndpoint)
        {
            if (listenerEndpoint.Port == ConfigManager.Config.Server.Network.Port + 1)
            {
                ServerPerformanceMonitor.RegisterEventStart(ServerPerformanceMonitor.MonitorType.ProcessPacket_1);
                if (packet.Header.Flags.HasFlag(PacketHeaderFlags.ConnectResponse))
                {
                    packetLog.Debug($"{packet}, {endPoint}");
                    PacketInboundConnectResponse connectResponse = new PacketInboundConnectResponse(packet);

                    // This should be set on the second packet to the server from the client.
                    // This completes the three-way handshake.
                    sessionLock.EnterReadLock();
                    Session session = null;
                    try
                    {
                        session =
                            (from k in sessionMap
                             where
                             k != null &&
                             k.State == SessionState.AuthConnectResponse &&
                             k.Network.ConnectionData.ConnectionCookie == connectResponse.Check &&
                             k.EndPoint.Address.Equals(endPoint.Address)
                             select k).FirstOrDefault();
                    }
                    finally
                    {
                        sessionLock.ExitReadLock();
                    }
                    if (session != null)
                    {
                        session.State = SessionState.AuthConnected;
                        session.Network.sendResync = true;
                        AuthenticationHandler.HandleConnectResponse(session);
                    }
                }
                else if (packet.Header.Id == 0 && packet.Header.HasFlag(PacketHeaderFlags.CICMDCommand))
                {
                    // TODO: Not sure what to do with these packets yet
                }
                else
                {
                    log.ErrorFormat("Packet from {0} rejected. Packet sent to listener 1 and is not a ConnectResponse or CICMDCommand", endPoint);
                }
                ServerPerformanceMonitor.RegisterEventEnd(ServerPerformanceMonitor.MonitorType.ProcessPacket_1);
            }
            else // ConfigManager.Config.Server.Network.Port + 0
            {
                ServerPerformanceMonitor.RegisterEventStart(ServerPerformanceMonitor.MonitorType.ProcessPacket_0);
                if (packet.Header.HasFlag(PacketHeaderFlags.LoginRequest))
                {
                    packetLog.Debug($"{packet}, {endPoint}");
                    if (GetSessionCount() >= ConfigManager.Config.Server.Network.MaximumAllowedSessions)
                    {
                        log.InfoFormat("Login Request from {0} rejected. Server full.", endPoint);
                        SendLoginRequestReject(endPoint, CharacterError.LogonServerFull);
                    }
                    else if (ServerManager.ShutdownInitiated && (ServerManager.ShutdownTime - DateTime.UtcNow).TotalMinutes < 2)
                    {
                        log.InfoFormat("Login Request from {0} rejected. Server shutting down in less than 2 minutes.", endPoint);
                        SendLoginRequestReject(endPoint, CharacterError.ServerCrash1);
                    }
                    else
                    {
                        log.DebugFormat("Login Request from {0}", endPoint);
                        var session = FindOrCreateSession(endPoint);
                        if (session != null)
                        {
                            if (session.State == SessionState.AuthConnectResponse)
                            {
                                // connect request packet sent to the client was corrupted in transit and session entered an unspecified state.
                                // ignore the request and remove the broken session and the client will start a new session.
                                RemoveSession(session);
                                log.Warn($"Bad handshake from {endPoint}, aborting session.");
                            }

                            session.ProcessPacket(packet);
                        }
                        else
                        {
                            log.InfoFormat("Login Request from {0} rejected. Failed to find or create session.", endPoint);
                            SendLoginRequestReject(endPoint, CharacterError.LogonServerFull);
                        }
                    }
                }
                else if (sessionMap.Length > packet.Header.Id)
                {
                    var session = sessionMap[packet.Header.Id];
                    if (session != null)
                    {
                        if (session.EndPoint.Equals(endPoint))
                        {
                            session.ProcessPacket(packet);
                        }
                        else
                        {
                            log.WarnFormat("Session for Id {0} has IP {1} but packet has IP {2}", packet.Header.Id, session.EndPoint, endPoint);
                        }
                    }
                    else
                    {
                        log.DebugFormat("Unsolicited Packet from {0} with Id {1}", endPoint, packet.Header.Id);
                    }
                }
                else
                {
                    log.DebugFormat("Unsolicited Packet from {0} with Id {1}", endPoint, packet.Header.Id);
                }
                ServerPerformanceMonitor.RegisterEventEnd(ServerPerformanceMonitor.MonitorType.ProcessPacket_0);
            }
        }