Exemple #1
0
        /// <summary>
        /// Processes physics objects in all active landblocks for updating
        /// </summary>
        private static IEnumerable <WorldObject> HandlePhysics(double timeTick)
        {
            ConcurrentQueue <WorldObject> movedObjects = new ConcurrentQueue <WorldObject>();

            try
            {
                var activeLandblocks = LandblockManager.GetActiveLandblocks();

                if (Concurrency)
                {
                    // Access ActiveLandblocks should be safe here, but sometimes crashes with
                    // System.InvalidOperationException: 'Collection was modified; enumeration operation may not execute.'
                    Parallel.ForEach(activeLandblocks, landblock =>
                    {
                        HandlePhysicsLandblock(landblock, timeTick, movedObjects);
                    });
                }
                else
                {
                    foreach (var landblock in activeLandblocks)
                    {
                        HandlePhysicsLandblock(landblock, timeTick, movedObjects);
                    }
                }
            }
            catch (Exception e)
            {
                log.Error(e);
            }
            return(movedObjects);
        }
        /// <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();

            UpdateGameWorld5MinRM.RegisterEventStart();
            UpdateGameWorld60MinRM.RegisterEventStart();

            // update positions through physics engine
            var movedObjects = HandlePhysics(Timers.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(Time.GetUnixTime());
            }

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

            UpdateGameWorld5MinRM.RegisterEventEnd();
            UpdateGameWorld60MinRM.RegisterEventEnd();

            if (UpdateGameWorld5MinRM.TotalSeconds > 300)
            {
                UpdateGameWorld5MinRM.ClearEventHistory();
                UpdateGameWorld5MinLastReset = DateTime.UtcNow;
            }

            if (UpdateGameWorld60MinRM.TotalSeconds > 3600)
            {
                UpdateGameWorld60MinRM.ClearEventHistory();
                UpdateGameWorld60MinLastReset = DateTime.UtcNow;
            }

            HouseManager.Tick();

            return(true);
        }
Exemple #3
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 activeLandblocks = LandblockManager.GetActiveLandblocks();

            foreach (var landblock in activeLandblocks)
            {
                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 #4
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;
        }
Exemple #5
0
        /// <summary>
        /// Threaded task created when performing a server shutdown
        /// </summary>
        private static void ShutdownServer()
        {
            var shutdownTime = DateTime.UtcNow.AddSeconds(ShutdownInterval);

            // wait for shutdown interval to expire
            while (shutdownTime != DateTime.MinValue && shutdownTime >= DateTime.UtcNow)
            {
                // this allows the server shutdown to be canceled
                if (!ShutdownInitiated)
                {
                    // reset shutdown details
                    string shutdownText = $"The server has canceled the shutdown procedure @ {DateTime.UtcNow} UTC";
                    log.Info(shutdownText);

                    // special text
                    foreach (var player in PlayerManager.GetAllOnline())
                    {
                        player.Session.WorldBroadcast(shutdownText);
                    }

                    // break function
                    return;
                }

                Thread.Sleep(10);
            }

            PropertyManager.StopUpdating();

            log.Debug("Logging off all players...");

            // logout each player
            foreach (var player in PlayerManager.GetAllOnline())
            {
                player.Session.LogOffPlayer();
            }

            log.Info("Waiting for all players to log off...");

            // wait 10 seconds for log-off
            while (PlayerManager.GetAllOnline().Count > 0)
            {
                Thread.Sleep(10);
            }

            log.Debug("Adding all landblocks to destruction queue...");

            // Queue unloading of all the landblocks
            // The actual unloading will happen in WorldManager.UpdateGameWorld
            LandblockManager.AddAllActiveLandblocksToDestructionQueue();

            log.Info("Waiting for all active landblocks to unload...");

            while (LandblockManager.GetActiveLandblocks().Count > 0)
            {
                Thread.Sleep(10);
            }

            log.Debug("Stopping world...");

            // Disabled thread update loop
            WorldManager.StopWorld();

            log.Info("Waiting for world to stop...");

            // Wait for world to end
            while (WorldManager.WorldActive)
            {
                Thread.Sleep(10);
            }

            log.Info("Saving OfflinePlayers that have unsaved changes...");
            PlayerManager.SaveOfflinePlayersWithChanges();

            log.Info("Waiting for database queue to empty...");

            // Wait for the database queue to empty
            while (DatabaseManager.Shard.QueueCount > 0)
            {
                Thread.Sleep(10);
            }

            // Write exit to console/log
            log.Info($"Exiting at {DateTime.UtcNow}");

            // System exit
            Environment.Exit(Environment.ExitCode);
        }