Пример #1
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;
        }
Пример #2
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");

            double lastTickDuration = 0d;

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

            while (!pendingWorldStop)
            {
                worldTickTimer.Restart();

                /*
                 * 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
                 *
                 * TODO: We need a thread safe way to handle object transitions between distant landblocks
                 */

                InboundClientMessageQueue.RunActions();

                playerEnterWorldQueue.RunActions();

                DelayManager.RunActions();

                // update positions through physics engine
                var movedObjects = HandlePhysics(PortalYearTicks);

                // iterate through objects that have changed landblocks
                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);
                }

                // Tick all of our Landblocks and WorldObjects
                var activeLandblocks = LandblockManager.GetActiveLandblocks();

                foreach (var landblock in activeLandblocks)
                {
                    landblock.Tick(lastTickDuration, Time.GetUnixTime());
                }

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

                // Session Maintenance
                int sessionCount;

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

                    // The session tick processes all inbound GameAction messages
                    foreach (var s in sessions)
                    {
                        s.Tick(lastTickDuration);
                    }

                    // Send the current time ticks to allow sessions to declare themselves bad
                    Parallel.ForEach(sessions, s => s.TickInParallel(lastTickDuration));

                    // Removes sessions in the NetworkTimeout state, incuding sessions that have reached a timeout limit.
                    var deadSessions = sessions.FindAll(s => s.State == Network.Enum.SessionState.NetworkTimeout);

                    foreach (var session in deadSessions)
                    {
                        log.Info($"client {session.Account} dropped");
                        RemoveSession(session);
                    }
                }
                finally
                {
                    sessionLock.ExitUpgradeableReadLock();
                }

                Thread.Sleep(sessionCount == 0 ? 10 : 1); // Relax the CPU if no sessions are connected

                lastTickDuration = worldTickTimer.Elapsed.TotalSeconds;
                PortalYearTicks += lastTickDuration;
            }

            // World has finished operations and concedes the thread to garbage collection
            WorldActive = false;
        }
Пример #3
0
        /// <summary>
        /// Manages updating all entities on the world.
        ///  - Server-side command-line commands are handled in their own thread.
        ///  - Network commands come from their own listener threads, and are queued in world objects
        ///  - This thread does the rest of the work!
        /// </summary>
        private static void UpdateWorld()
        {
            log.DebugFormat("Starting UpdateWorld thread");
            double lastTick = 0d;

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

            while (!pendingWorldStop)
            {
                worldTickTimer.Restart();

                // Handle time-based timeouts
                DelayManager.RunActions();

                // Sequences of update thread:
                // Update positions based on new tick
                // TODO(ddevec): Physics here
                IEnumerable <WorldObject> movedObjects = FakePhysics(PortalYearTicks);

                // Do any pre-calculated landblock transfers --
                foreach (WorldObject wo in movedObjects)
                {
                    // If it was picked up, or moved
                    // NOTE: The object's Location can now be null, if a player logs out, or an item is picked up
                    if (wo.Location != null && wo.Location.LandblockId != wo.CurrentLandblock.Id)
                    {
                        // NOTE: We are moving the objects on behalf of the physics
                        LandblockManager.RelocateObjectForPhysics(wo);
                    }
                }

                // FIXME(ddevec): This O(n^2) tracking loop is a remenant of the old structure -- we should probably come up with a more efficient tracking scheme
                Parallel.ForEach(movedObjects, mo =>
                {
                    // detect all world objects in ghost range
                    List <WorldObject> woproxghost = new List <WorldObject>();
                    woproxghost.AddRange(mo.CurrentLandblock.GetWorldObjectsInRangeForPhysics(mo, Landblock.MaxObjectGhostRange));

                    // for all objects in range of this moving object or in ghost range of moving object update them.
                    Parallel.ForEach(woproxghost, gwo =>
                    {
                        if (mo.Guid.IsPlayer())
                        {
                            // if world object is in active zone then.
                            if (gwo.Location.SquaredDistanceTo(mo.Location) <= Landblock.MaxObjectRange * Landblock.MaxObjectRange)
                            {
                                // if world object is in active zone.
                                if (!(mo as Player).GetTrackedObjectGuids().Contains(gwo.Guid))
                                {
                                    (mo as Player).TrackObject(gwo);
                                }
                            }
                            // if world object is in ghost zone and outside of active zone
                            else
                            {
                                if ((mo as Player).GetTrackedObjectGuids().Contains(gwo.Guid))
                                {
                                    (mo as Player).StopTrackingObject(gwo, false);
                                }
                            }
                        }
                    });
                });

                // Process between landblock object motions sequentially
                // Currently only used for picking items up off a landblock
                MotionQueue.RunActions();

                // Now, update actions within landblocks
                //   This is responsible for updating all "actors" residing within the landblock.
                //   Objects and landblocks are "actors"
                //   "actors" decide if they want to read/modify their own state (set desired velocity), move-to positions, move items, read vitals, etc
                // N.B. -- Broadcasts are enqueued for sending at the end of the landblock's action time
                // FIXME(ddevec): Goal is to eventually migrate to an "Act" function of the LandblockManager ActiveLandblocks
                //    Inactive landblocks will be put on TimeoutManager queue for timeout killing
                ActionQueue.RunActions();

                // Handles sending out all per-landblock broadcasts -- This may rework when we rework tracking -- tbd
                BroadcastQueue.RunActions();

                // XXX(ddevec): Should this be its own step in world-update thread?
                sessionLock.EnterReadLock();
                try
                {
                    // Send the current time ticks to allow sessions to declare themselves bad
                    Parallel.ForEach(sessions, s => s.Update(lastTick, DateTime.UtcNow.Ticks));
                }
                finally
                {
                    sessionLock.ExitReadLock();
                }

                // Removes sessions in the NetworkTimeout state, incuding sessions that have reached a timeout limit.
                var deadSessions = sessions.FindAll(s => s.State == Network.Enum.SessionState.NetworkTimeout);
                if (deadSessions.Count > 0)
                {
                    Parallel.ForEach(deadSessions, RemoveSession);
                }

                Thread.Sleep(1);

                lastTick         = (double)worldTickTimer.ElapsedTicks / Stopwatch.Frequency;
                PortalYearTicks += lastTick;
            }

            // World has finished operations and concedes the thread to garbage collection
            WorldActive = false;
        }
Пример #4
0
        /// <summary>
        /// Manages updating all entities on the world.
        ///  - Server-side command-line commands are handled in their own thread.
        ///  - Network commands come from their own listener threads, and are queued in world objects
        ///  - This thread does the rest of the work!
        /// </summary>
        private static void UpdateWorld()
        {
            log.DebugFormat("Starting UpdateWorld thread");
            double lastTick = 0d;

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

            while (!pendingWorldStop)
            {
                worldTickTimer.Restart();

                // handle time-based actions
                DelayManager.RunActions();

                // update positions through physics engine
                var movedObjects = HandlePhysics(PortalYearTicks);

                // iterate through objects that have changed landblocks
                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);
                }

                InboundMessageQueue.RunActions();

                // Process between landblock object motions sequentially
                // Currently only used for picking items up off a landblock
                LandblockMotionQueue.RunActions();

                // Now, update actions within landblocks
                //   This is responsible for updating all "actors" residing within the landblock.
                //   Objects and landblocks are "actors"
                //   "actors" decide if they want to read/modify their own state (set desired velocity), move-to positions, move items, read vitals, etc
                // N.B. -- Broadcasts are enqueued for sending at the end of the landblock's action time
                // FIXME(ddevec): Goal is to eventually migrate to an "Act" function of the LandblockManager ActiveLandblocks
                //    Inactive landblocks will be put on TimeoutManager queue for timeout killing
                LandblockActionQueue.RunActions();

                // Handles sending out all per-landblock broadcasts -- This may rework when we rework tracking -- tbd
                LandblockBroadcastQueue.RunActions();

                // XXX(ddevec): Should this be its own step in world-update thread?
                sessionLock.EnterReadLock();
                try
                {
                    // Send the current time ticks to allow sessions to declare themselves bad
                    Parallel.ForEach(sessions, s => s.Update(lastTick, DateTime.UtcNow.Ticks));
                }
                finally
                {
                    sessionLock.ExitReadLock();
                }

                // Removes sessions in the NetworkTimeout state, incuding sessions that have reached a timeout limit.
                var deadSessions = sessions.FindAll(s => s.State == Network.Enum.SessionState.NetworkTimeout);
                if (deadSessions.Count > 0)
                {
                    Parallel.ForEach(deadSessions, RemoveSession);
                }

                Thread.Sleep(1);

                lastTick         = (double)worldTickTimer.ElapsedTicks / Stopwatch.Frequency;
                PortalYearTicks += lastTick;

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

            // World has finished operations and concedes the thread to garbage collection
            WorldActive = false;
        }