/// <summary> /// Creates an instance object. This shouldn't be used directly - Please use WorldMgr.CreateInstance /// to create an instance. /// </summary> public BaseInstance(ushort ID, GameTimer.TimeManager time, RegionData data) :base(time, data) { m_regionID = ID; m_skinID = data.Id; //Notify we've created an instance. log.Warn("An instance is created! " + Name + ", RegionID: " + ID + ", SkinID: " + Skin); }
/// <summary> /// Removes the timer from the table. /// </summary> /// <param name="timer">The timer to remove</param> internal void RemoveTimer(GameTimer timer) { lock (m_buckets) { RemoveTimerUnsafe(timer); } }
/// <summary> /// Inserts the timer into the table. /// </summary> /// <param name="t">The timer to insert</param> /// <param name="offsetTick">The offset from current tick. min value=1, max value<MaxInterval</param> internal void InsertTimer(GameTimer t, int offsetTick) { if (offsetTick > MaxInterval || offsetTick < 1) throw new ArgumentOutOfRangeException("offsetTick", offsetTick.ToString(), "Offset must be in range from 1 to "+MaxInterval); GameTimer timer = t; lock (m_buckets) { long timerTick = timer.m_tick; long targetTick = m_tick + offsetTick; if (timerTick == m_tick || (timerTick & TIMER_RESCHEDULED) != 0) { timer.m_targetTime = (int) (CurrentTime + offsetTick); timer.m_tick = targetTick | TIMER_RESCHEDULED; return; } if ((timerTick & TIMER_DISABLED) == 0) { RemoveTimerUnsafe(timer); } timer.m_targetTime = (int) (CurrentTime + offsetTick); m_activeTimers++; if (offsetTick <= CACHE_MASK + 1) { timer.m_tick = targetTick & TICK_MASK; targetTick &= CACHE_MASK; CacheBucket bucket = m_cachedBucket[targetTick]; GameTimer prev = bucket.LastTimer; if (prev != null) { prev.m_nextTimer = timer; m_cachedBucket[targetTick].LastTimer = timer; } else { bucket.FirstTimer = timer; bucket.LastTimer = timer; m_cachedBucket[targetTick] = bucket; } } else { if ((targetTick & TICK_MASK) > (m_tick & ~BUCKET_MASK) + BUCKET_MASK) targetTick += TICK_MASK + 1; // extra pass if the timer is ahead of current tick timer.m_tick = targetTick; targetTick = (targetTick >> BUCKET_BITS) & TABLE_MASK; GameTimer next = m_buckets[targetTick]; m_buckets[targetTick] = timer; if (next != null) { timer.m_nextTimer = next; } } } }
/// <summary> /// Checks one timers chain /// </summary> /// <param name="t"></param> /// <param name="tableName"></param> /// <returns></returns> private static int CheckChain(GameTimer t, string tableName) { int res = 0; while (t != null) { if (res++ > 500000) { log.WarnFormat("possible circular chain (500000 timers in one bucket)"); break; } t = t.m_nextTimer; } return res; }
/// <summary> /// Call this function to close the door /// </summary> public virtual void Close(GameLiving closer = null) { if (!m_openDead) this.State = eDoorState.Closed; m_closeDoorAction = null; }
/// <summary> /// Call this function to open the door /// </summary> public virtual void Open(GameLiving opener = null) { if (Locked == 0) this.State = eDoorState.Open; if (HealthPercent > 40 || !m_openDead) { lock (m_LockObject) { if (m_closeDoorAction == null) { m_closeDoorAction = new CloseDoorAction(this); } m_closeDoorAction.Start(CLOSE_DOOR_TIME); } } }
/// <summary> /// Check if this player can be updated /// </summary> /// <param name="lastUpdate"></param> /// <returns></returns> private static bool PlayerNeedUpdate(long lastUpdate) { return((GameTimer.GetTickCount() - lastUpdate) >= GetPlayerWorldUpdateInterval); }
/// <summary> /// RegionInstance Constructor /// </summary> /// <param name="player"></param> public RegionInstance(ushort ID, GameTimer.TimeManager time, RegionData dat) : base(ID, time, dat) { this.m_players_in = new List<GamePlayer>(); this.DestroyWhenEmpty = false; }
/// <summary> /// Constructs a new empty Region /// </summary> /// <param name="time">The time manager for this region</param> /// <param name="data">The region data</param> public Region(GameTimer.TimeManager time, RegionData data) { m_regionData = data; m_objects = new GameObject[0]; m_objectsInRegion = 0; m_nextObjectSlot = 0; m_objectsAllocatedSlots = new uint[0]; m_graveStones = new Hashtable(); m_zones = new ReaderWriterList<Zone>(1); m_ZoneAreas = new ushort[64][]; m_ZoneAreasCount = new ushort[64]; for (int i = 0; i < 64; i++) { m_ZoneAreas[i] = new ushort[AbstractArea.MAX_AREAS_PER_ZONE]; } m_Areas = new Dictionary<ushort, IArea>(); m_timeManager = time; List<string> list = null; if (ServerProperties.Properties.DEBUG_LOAD_REGIONS != string.Empty) list = ServerProperties.Properties.DEBUG_LOAD_REGIONS.SplitCSV(true); if (list != null && list.Count > 0) { m_loadObjects = false; foreach (string region in list) { if (region.ToString() == ID.ToString()) { m_loadObjects = true; break; } } } list = ServerProperties.Properties.DISABLED_REGIONS.SplitCSV(true); foreach (string region in list) { if (region.ToString() == ID.ToString()) { m_isDisabled = true; break; } } list = ServerProperties.Properties.DISABLED_EXPANSIONS.SplitCSV(true); foreach (string expansion in list) { if (expansion.ToString() == m_regionData.Expansion.ToString()) { m_isDisabled = true; break; } } }
/// <summary> /// Call this function to close the door /// </summary> public void Close() { if (!m_openDead) this.State = eDoorState.Closed; m_closeDoorAction = null; }
/// <summary> /// Creates and adds a new region to the WorldMgr /// </summary> /// <param name="time">Time manager for the region</param> /// <param name="data">The region data</param> /// <returns>Registered region</returns> public static Region RegisterRegion(GameTimer.TimeManager time, RegionData data) { Region region = Region.Create(time, data); lock (m_regions.SyncRoot) { m_regions.Add(data.Id, region); } return region; }
/// <summary> /// Try to Send Tooltip to Client, return false if cache hit. /// Return true and register cache before you can send tooltip ! /// </summary> /// <param name="type"></param> /// <param name="id"></param> /// <returns></returns> public bool CanSendTooltip(int type, int id) { m_tooltipRequestTimes.TryAdd(type, new ConcurrentDictionary <int, long>()); // Queries cleanup foreach (Tuple <int, int> keys in m_tooltipRequestTimes.SelectMany(e => e.Value.Where(it => it.Value < GameTimer.GetTickCount()).Select(el => new Tuple <int, int>(e.Key, el.Key)))) { long dummy; m_tooltipRequestTimes[keys.Item1].TryRemove(keys.Item2, out dummy); } // Query hit ? if (m_tooltipRequestTimes[type].ContainsKey(id)) { return(false); } // Query register m_tooltipRequestTimes[type].TryAdd(id, GameTimer.GetTickCount() + 3600000); return(true); }
/// <summary> /// This thread updates the NPCs and objects around the player at very short /// intervalls! But since the update is very quick the thread will /// sleep most of the time! /// </summary> public static void WorldUpdateThreadStart() { // Tasks Collection of running Player updates, with starting time. var clientsUpdateTasks = new Dictionary <GameClient, Tuple <long, Task, Region> >(); bool running = true; if (log.IsInfoEnabled) { log.InfoFormat("World Update Thread Starting - ThreadId = {0}", Thread.CurrentThread.ManagedThreadId); } while (running) { try { // Start Time of the loop long begin = GameTimer.GetTickCount(); // Get All Clients var clients = WorldMgr.GetAllClients(); // Clean Tasks Dict on Client Exiting. foreach (GameClient cli in clientsUpdateTasks.Keys.ToArray()) { if (cli == null) { continue; } GamePlayer player = cli.Player; bool notActive = cli.ClientState != GameClient.eClientState.Playing || player == null || player.ObjectState != GameObject.eObjectState.Active; bool notConnected = !clients.Contains(cli); if (notConnected || (notActive && IsTaskCompleted(cli, clientsUpdateTasks))) { clientsUpdateTasks.Remove(cli); cli.GameObjectUpdateArray.Clear(); cli.HouseUpdateArray.Clear(); } } // Browse all clients to check if they can be updated. for (int cl = 0; cl < clients.Count; cl++) { GameClient client = clients[cl]; // Check that client is healthy if (client == null) { continue; } GamePlayer player = client.Player; if (client.ClientState == GameClient.eClientState.Playing && player == null) { if (log.IsErrorEnabled) { log.Error("account has no active player but is playing, disconnecting! => " + client.Account.Name); } // Disconnect buggy Client GameServer.Instance.Disconnect(client); continue; } // Check that player is active. if (client.ClientState != GameClient.eClientState.Playing || player == null || player.ObjectState != GameObject.eObjectState.Active) { continue; } // Start Update Task StartPlayerUpdateTask(client, clientsUpdateTasks, begin); } long took = GameTimer.GetTickCount() - begin; if (took >= 500) { if (log.IsWarnEnabled) { log.WarnFormat("World Update Thread (NPC/Object update) took {0} ms", took); } } // relaunch update thread every 100 ms to check if any player need updates. Thread.Sleep((int)Math.Max(1, 100 - took)); } catch (ThreadInterruptedException) { if (log.IsInfoEnabled) { log.Info("World Update Thread stopping..."); } running = false; break; } catch (Exception e) { if (log.IsErrorEnabled) { log.Error("Error in World Update (NPC/Object Update) Thread!", e); } } } }
private static bool StartPlayerUpdateTask(GameClient client, IDictionary <GameClient, Tuple <long, Task, Region> > clientsUpdateTasks, long begin) { var player = client.Player; // Check for existing Task Tuple <long, Task, Region> clientEntry; if (!clientsUpdateTasks.TryGetValue(client, out clientEntry)) { // Client not in tasks, create it and run it ! clientEntry = new Tuple <long, Task, Region>(begin, Task.Factory.StartNew(() => UpdatePlayerWorld(player)), player.CurrentRegion); // Register. clientsUpdateTasks.Add(client, clientEntry); return(true); } else { // Get client entry data. long lastUpdate = clientEntry.Item1; Task taskEntry = clientEntry.Item2; Region lastRegion = clientEntry.Item3; //Check if task finished if (!taskEntry.IsCompleted) { // Check for how long if ((begin - lastUpdate) > GetPlayerWorldUpdateInterval) { if (log.IsWarnEnabled && (GameTimer.GetTickCount() - player.TempProperties.getProperty <long>("LAST_WORLD_UPDATE_THREAD_WARNING", 0) >= 1000)) { log.WarnFormat("Player Update Task ({0}) Taking more than world update refresh rate : {1} ms (real {2} ms) - Task Status : {3}!", player.Name, GetPlayerWorldUpdateInterval, begin - lastUpdate, taskEntry.Status); player.TempProperties.setProperty("LAST_WORLD_UPDATE_THREAD_WARNING", GameTimer.GetTickCount()); } } // Don't init this client. return(false); } // Display Exception if (taskEntry.IsFaulted) { if (log.IsErrorEnabled) { log.ErrorFormat("Error in World Update Thread, Player Task ({0})! Exception : {1}", player.Name, taskEntry.Exception); } } // Region Refresh if (player.CurrentRegion != lastRegion) { lastUpdate = 0; lastRegion = player.CurrentRegion; client.GameObjectUpdateArray.Clear(); client.HouseUpdateArray.Clear(); } // If this player need update. if (PlayerNeedUpdate(lastUpdate)) { // Update Time, Region and Create Task var newClientEntry = new Tuple <long, Task, Region>(begin, Task.Factory.StartNew(() => UpdatePlayerWorld(player)), lastRegion); // Register Tuple clientsUpdateTasks[client] = newClientEntry; return(true); } } return(false); }
/// <summary> /// Removes the timer from the table without locking the table /// </summary> /// <param name="timer">The timer to remove</param> private void RemoveTimerUnsafe(GameTimer timer) { GameTimer t = timer; long tick = t.m_tick; if ((tick & TIMER_DISABLED) != 0) return; timer.m_targetTime = -1; // never change the active chain if (tick == m_tick || (tick & TIMER_RESCHEDULED) != 0) { t.m_tick = TIMER_DISABLED | TIMER_RESCHEDULED; return; } m_activeTimers--; // check the cache first long cachedIndex = tick & CACHE_MASK; CacheBucket bucket = m_cachedBucket[cachedIndex]; if (bucket.FirstTimer == t) { t.m_tick = TIMER_DISABLED; bucket.FirstTimer = t.m_nextTimer; if (bucket.LastTimer == t) bucket.LastTimer = t.m_nextTimer; t.m_nextTimer = null; m_cachedBucket[cachedIndex] = bucket; return; } GameTimer timerChain = bucket.FirstTimer; GameTimer prev; while (timerChain != null) { prev = timerChain; timerChain = timerChain.m_nextTimer; if (timerChain == t) { prev.m_nextTimer = t.m_nextTimer; t.m_nextTimer = null; t.m_tick = TIMER_DISABLED; if (bucket.LastTimer == t) { bucket.LastTimer = prev; m_cachedBucket[cachedIndex] = bucket; } return; } } // check the buckets tick = (tick >> BUCKET_BITS) & TABLE_MASK; timerChain = m_buckets[tick]; if (timerChain == t) { timerChain = timerChain.m_nextTimer; m_buckets[tick] = timerChain; t.m_nextTimer = null; t.m_tick = TIMER_DISABLED; return; } while (timerChain != null) { prev = timerChain; timerChain = timerChain.m_nextTimer; if (timerChain == t) { prev.m_nextTimer = t.m_nextTimer; break; } } t.m_nextTimer = null; t.m_tick = TIMER_DISABLED; }
/// <summary> /// Factory method to create regions. Will create a region of data.ClassType, or default to Region if /// an error occurs or ClassType is not specified /// </summary> /// <param name="time"></param> /// <param name="data"></param> /// <returns></returns> public static Region Create(GameTimer.TimeManager time, RegionData data) { try { Type t = typeof(Region); if (string.IsNullOrEmpty(data.ClassType) == false) { t = Type.GetType(data.ClassType); if (t == null) { t = ScriptMgr.GetType(data.ClassType); } if (t != null) { ConstructorInfo info = t.GetConstructor(new Type[] { typeof(GameTimer.TimeManager), typeof(RegionData) }); Region r = (Region)info.Invoke(new object[] { time, data }); if (r != null) { // Success with requested classtype log.InfoFormat("Created Region {0} using ClassType '{1}'", r.ID, data.ClassType); return r; } log.ErrorFormat("Failed to Invoke Region {0} using ClassType '{1}'", r.ID, data.ClassType); } else { log.ErrorFormat("Failed to find ClassType '{0}' for region {1}!", data.ClassType, data.Id); } } } catch (Exception ex) { log.ErrorFormat("Failed to start region {0} with requested classtype: {1}. Exception: {2}!", data.Id, data.ClassType, ex.Message); } // Create region using default type return new Region(time, data); }
/// <summary> /// The time thread loop /// </summary> private void TimeThread() { log.InfoFormat("started timer thread {0} (ID:{1})", m_name, Thread.CurrentThread.ManagedThreadId); int timeBalance = 0; uint workStart, workEnd; GameTimer chain, next, bucketTimer; workStart = workEnd = (uint)GetTickCount(); #if MonitorCallbacks Timer t = new Timer(new TimerCallback(SlowTimerCallback), null, Timeout.Infinite, Timeout.Infinite); #endif while (m_running) { try { // fire timers lock (m_buckets) { if(DOL.GS.GameEvents.RegionTimersResynch.watch!=null) m_time = DOL.GS.GameEvents.RegionTimersResynch.watch.ElapsedMilliseconds; else m_time++; int newTick = m_tick = (m_tick + 1) & TICK_MASK; if ((newTick & BUCKET_MASK) == 0) { // cache next bucket int index = newTick >> BUCKET_BITS; next = m_buckets[index]; if (next != null) { m_buckets[index] = null; // sort the new cached bucket do { GameTimer timer = next; next = next.m_nextTimer; long index2 = timer.m_tick; if ((index2 & LONGTERM_MASK) != 0 && ((index2 -= (1 << TABLE_BITS + BUCKET_BITS)) & LONGTERM_MASK) != 0) { // reinsert longterm timers back timer.m_tick = index2; bucketTimer = m_buckets[index]; m_buckets[index] = timer; } else { timer.m_tick = index2; index2 &= CACHE_MASK; bucketTimer = m_cachedBucket[index2].FirstTimer; m_cachedBucket[index2].FirstTimer = timer; if (m_cachedBucket[index2].LastTimer == null) m_cachedBucket[index2].LastTimer = timer; } if (bucketTimer == null) { timer.m_nextTimer = null; } else { timer.m_nextTimer = bucketTimer; } } while (next != null); } } int cacheIndex = m_tick & CACHE_MASK; chain = m_cachedBucket[cacheIndex].FirstTimer; if (chain != null) { m_cachedBucket[cacheIndex] = CacheBucket.EmptyBucket; } } GameTimer current = chain; int curTick = m_tick; int currentBucketMax = (curTick & ~BUCKET_MASK) + BUCKET_MASK; while (current != null) { if (current.m_tick == curTick) { try { long callbackStart = GetTickCount(); #if MonitorCallbacks m_currentTimer = current; m_timerTickStart = callbackStart; t.Change(200, 100); #endif current.OnTick(); #if MonitorCallbacks m_currentTimer = null; t.Change(Timeout.Infinite, Timeout.Infinite); if (GetTickCount() - callbackStart > 200) { lock (m_delayLog) { m_delayLog.Write("\n========================================================\n\n"); } } #endif if (GetTickCount() - callbackStart > 250) { if (log.IsWarnEnabled) { string curStr; try { curStr = current.ToString(); } catch(Exception ee) { curStr = "error in timer.ToString(): " + current.GetType().FullName + "; " + ee.ToString(); } string warning = "callback took "+(GetTickCount() - callbackStart)+"ms! "+curStr; log.Warn(warning); } } #if CollectStatistic // statistic int start = GetTickCount(); string callback; if (current is RegionTimer) { callback = ((RegionTimer)current).Callback.Method.ToString(); } else { callback = current.GetType().FullName; } lock (m_timerCallbackStatistic) { object obj = m_timerCallbackStatistic[callback]; if (obj == null) { m_timerCallbackStatistic[callback] = 1; } else { m_timerCallbackStatistic[callback] = ((int)obj) + 1; } } if (GetTickCount()-start > 500) { if (log.IsWarnEnabled) log.Warn("Ticker statistic "+callback+" took more than 500ms!"); } #endif } catch (Exception e) { string curStr; try { curStr = current.ToString(); } catch(Exception ee) { curStr = "error in timer.ToString(): " + current.GetType().FullName + "; " + ee.ToString(); } if (log.IsErrorEnabled) log.Error("Timer callback (" + curStr + ")", e); current.m_tick = TIMER_DISABLED | TIMER_RESCHEDULED; } m_invokedCount++; } else if ((current.m_tick & TIMER_RESCHEDULED) == 0) { //log.ErrorFormat("timer tick != current tick (0x{0:X4}), fired anyway: {1}", curTick, current); try { current.OnTick(); } catch (Exception e) { log.Error("timer error", e); current.m_tick = TIMER_DISABLED | TIMER_RESCHEDULED; } } lock (m_buckets) { next = current.m_nextTimer; long tick = current.m_tick; long interval = current.m_interval; if ((tick & TIMER_DISABLED) != 0 || (interval == 0 && (tick & TIMER_RESCHEDULED) == 0)) { m_activeTimers--; current.m_nextTimer = null; current.m_tick = TIMER_DISABLED; current.m_targetTime = -1; } else { ///// REINSERT all including rescheduled timers if ((tick & TIMER_RESCHEDULED) != 0) { current.m_tick = tick &= ~TIMER_RESCHEDULED; } else { current.m_targetTime = (int) (CurrentTime + interval); current.m_tick = tick = curTick + interval; } if (tick - curTick <= CACHE_MASK + 1) { tick &= CACHE_MASK; current.m_tick &= TICK_MASK; CacheBucket bucket = m_cachedBucket[tick]; GameTimer prev = bucket.LastTimer; current.m_nextTimer = null; if (prev != null) { prev.m_nextTimer = current; bucket.LastTimer = current; } else { bucket.FirstTimer = current; bucket.LastTimer = current; } m_cachedBucket[tick] = bucket; } else { if ((tick & TICK_MASK) > currentBucketMax) current.m_tick = tick += TICK_MASK + 1; // extra pass if the timer is ahead of current tick tick = (tick >> BUCKET_BITS) & TABLE_MASK; bucketTimer = m_buckets[tick]; if (bucketTimer == null) { current.m_nextTimer = null; } else { current.m_nextTimer = bucketTimer; } m_buckets[tick] = current; } ///// } } current = next; } bucketTimer = null; workEnd = (uint)GetTickCount(); timeBalance += 1 - (int)(workEnd - workStart); if (timeBalance > 0) { Thread.Sleep(timeBalance); workStart = (uint)GetTickCount(); timeBalance -= (int)(workStart - workEnd); } else { if (timeBalance < -1000) { //We can not increase forever if we get out of //sync. At some point we have to print out a warning //and catch up some time! if (log.IsWarnEnabled && timeBalance < -2000) { // Again, too much warning spam is meaningless. Will warn if time sync is more than the typical 1 to 2 seconds // -tolakram log.Warn(Name + " out of sync, over 2000ms lost! " + timeBalance.ToString()); } timeBalance += 1000; } workStart = workEnd; } } catch (ThreadAbortException e) { if (log.IsWarnEnabled) log.Warn("Time manager thread \"" + m_name + "\" was aborted", e); m_running = false; break; } catch (Exception e) { if (log.IsErrorEnabled) log.Error("Exception in time manager \"" + m_name + "\"!", e); } } log.InfoFormat("stopped timer thread {0} (ID:{1})", m_name, Thread.CurrentThread.ManagedThreadId); }
/// <summary> /// AdventureWingInstance Constructor /// </summary> public AdventureWingInstance(ushort ID, GameTimer.TimeManager time, RegionData dat) : base(ID, time, dat) { }
/// <summary> /// Creates an instance object. This shouldn't be used directly - Please use WorldMgr.CreateInstance /// to create an instance. /// </summary> public Instance(ushort ID, GameTimer.TimeManager time, RegionData data) :base(ID, time, data) { }
/// <summary> /// Update all World Around Player /// </summary> /// <param name="player">The player needing update</param> private static void UpdatePlayerWorld(GamePlayer player) { UpdatePlayerWorld(player, GameTimer.GetTickCount()); }