feat: Adds tidy option for serialization. Codegens ballotbox. Fixes pooled timer leaking (#681)

* Fixes pooled timer leaking
* Fixes `[dumptimers` command so it outputs properly, adds spacing, and stacktraces
* Adds `[Tidy]` for serializing Lists. This will remove deleted entities during world save before serializing the list.
* Adds helpers for managing Lists/Sets/Dictionaries


### New API

```cs
// Creates the list if it is null, then adds
Utility.Add(ref list, value);
Utility.Add(ref set, value);
Utility.Add(ref dict, key, value);

// Nulls the variable if the count is zero
Utility.Remove(ref list, value);
Utility.Remove(ref set, value);
Utility.Remove(ref dict, key);

// Marks entity as dirty in addition to doing the action
entity.Add(list, value);
// Marks entity as dirty, and will create list if it doesn't exist
entity.Add(ref list, value);

// Marks entity as dirty in addition to doing the action
entity.Remove(list, value);
// Marks entity as dirty, and will null the list count is zero
entity.Remove(ref list, value);
```


### Updates to [dumptimers
<img width="825" alt="Screen Shot 2021-08-14 at 2 55 10 AM" src="https://user-images.githubusercontent.com/3953314/129442449-ccf7fe14-29d6-4f3f-9366-c8eb7b9828a7.png">
This commit is contained in:
Kamron Batman 2021-08-14 03:02:53 -07:00 • committed by GitHub
parent 84cbd52a2a
commit 360143478a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
53 changed files with 627 additions and 464 deletions

View file

@ -70,7 +70,7 @@ namespace Server.Accounting
private set
{
_comments = value;
((ISerializable)this).MarkDirty();
this.MarkDirty();
}
}
@ -83,7 +83,7 @@ namespace Server.Accounting
private set
{
_tags = value;
((ISerializable)this).MarkDirty();
this.MarkDirty();
}
}
@ -121,7 +121,7 @@ namespace Server.Accounting
private set
{
_totalGameTime = value;
((ISerializable)this).MarkDirty();
this.MarkDirty();
}
}
@ -148,7 +148,7 @@ namespace Server.Accounting
_loginIPs = Array.Empty<IPAddress>();
Accounts.Add(this);
((ISerializable)this).MarkDirty();
this.MarkDirty();
}
public Account(XmlElement node)
@ -224,7 +224,7 @@ namespace Server.Accounting
}
Accounts.Add(this);
((ISerializable)this).MarkDirty();
this.MarkDirty();
}
public void SetTypeRef(Type type)
@ -527,7 +527,7 @@ namespace Server.Accounting
// outside of an entire account deletion.
m.Account = null;
_mobiles[index] = null;
((ISerializable)this).MarkDirty();
this.MarkDirty();
}
return null;
@ -542,7 +542,7 @@ namespace Server.Accounting
}
_mobiles[index] = value;
((ISerializable)this).MarkDirty();
this.MarkDirty();
if (_mobiles[index] != null)
{
@ -677,7 +677,7 @@ namespace Server.Accounting
public void AddTag(string name, string value)
{
Tags.Add(new AccountTag(name, value));
((ISerializable)this).MarkDirty();
this.MarkDirty();
}
/// <summary>
@ -698,7 +698,7 @@ namespace Server.Accounting
if (tag.Name == name)
{
_tags?.RemoveAt(i);
((ISerializable)this).MarkDirty();
this.MarkDirty();
}
}
}
@ -717,7 +717,7 @@ namespace Server.Accounting
if (tag.Name == name)
{
tag.Value = value;
((ISerializable)this).MarkDirty();
this.MarkDirty();
return;
}
}

View file

@ -78,6 +78,7 @@ namespace Server.Commands
{
using var sw = new StreamWriter("timerdump.log", true);
Timer.DumpInfo(sw);
e.Mobile.SendMessage("Timers dumped to timerdump.log");
}
catch
{

View file

@ -13,7 +13,7 @@ namespace Server.Factions
public static readonly TimeSpan CampaignPeriod = TimeSpan.FromDays(1.0);
public static readonly TimeSpan VotingPeriod = TimeSpan.FromDays(3.0);
private TimerExecutionToken _timerToken;
private Timer _timer;
public Election(Faction faction)
{
@ -109,7 +109,7 @@ namespace Server.Factions
public void StartTimer()
{
Timer.StartTimer(TimeSpan.FromMinutes(1.0), TimeSpan.FromMinutes(1.0), Slice, out _timerToken);
_timer = Timer.DelayCall(TimeSpan.FromMinutes(1.0), TimeSpan.FromMinutes(1.0), Slice);
}
public void Serialize(IGenericWriter writer)
@ -274,7 +274,8 @@ namespace Server.Factions
{
if (Faction.Election != this)
{
_timerToken.Cancel();
_timer?.Stop();
_timer = null;
return;
}

View file

@ -621,9 +621,8 @@ namespace Server.Factions
EventSink.Login += EventSink_Login;
EventSink.Logout += EventSink_Logout;
Timer.StartTimer(TimeSpan.FromMinutes(1.0), TimeSpan.FromMinutes(10.0), HandleAtrophy);
Timer.StartTimer(TimeSpan.FromSeconds(30.0), TimeSpan.FromSeconds(30.0), ProcessTick);
Timer.DelayCall(TimeSpan.FromMinutes(1.0), TimeSpan.FromMinutes(10.0), HandleAtrophy);
Timer.DelayCall(TimeSpan.FromSeconds(30.0), TimeSpan.FromSeconds(30.0), ProcessTick);
CommandSystem.Register("FactionElection", AccessLevel.GameMaster, FactionElection_OnCommand);
CommandSystem.Register("FactionCommander", AccessLevel.Administrator, FactionCommander_OnCommand);

View file

@ -91,10 +91,9 @@ namespace Server.Items
return;
}
_from = from.Name;
_to = m.Name;
From = from.Name;
To = m.Name;
((ISerializable)this).MarkDirty();
InvalidateProperties();
from.SendMessage("You inscribe the arrow.");

View file

@ -7,7 +7,8 @@ using Server.Prompts;
namespace Server.Items
{
public class BallotBox : AddonComponent
[Serializable(0)]
public partial class BallotBox : AddonComponent
{
public static readonly int MaxTopicLines = 6;
@ -19,17 +20,18 @@ namespace Server.Items
No = new List<Mobile>();
}
public BallotBox(Serial serial) : base(serial)
{
}
public override int LabelNumber => 1041006; // a ballot box
public string[] Topic { get; private set; }
[SerializableField(0, setter: "private")]
private string[] _topic;
public List<Mobile> Yes { get; private set; }
[Tidy]
[SerializableField(1, setter: "private")]
private List<Mobile> _yes;
public List<Mobile> No { get; private set; }
[Tidy]
[SerializableField(2, setter: "private")]
private List<Mobile> _no;
public void ClearTopic()
{
@ -56,8 +58,11 @@ namespace Server.Items
public void ClearVotes()
{
Yes.Clear();
No.Clear();
if (Yes.Count > 0 || No.Count > 0)
{
this.Clear(_yes);
this.Clear(_no);
}
}
public bool IsOwner(Mobile from)
@ -92,43 +97,6 @@ namespace Server.Items
}
}
public override void Serialize(IGenericWriter writer)
{
base.Serialize(writer);
writer.WriteEncodedInt(0); // version
writer.WriteEncodedInt(Topic.Length);
for (var i = 0; i < Topic.Length; i++)
{
writer.Write(Topic[i]);
}
Yes.Tidy();
writer.Write(Yes);
No.Tidy();
writer.Write(No);
}
public override void Deserialize(IGenericReader reader)
{
base.Deserialize(reader);
var version = reader.ReadEncodedInt();
Topic = new string[reader.ReadEncodedInt()];
for (var i = 0; i < Topic.Length; i++)
{
Topic[i] = reader.ReadString();
}
Yes = reader.ReadEntityList<Mobile>();
No = reader.ReadEntityList<Mobile>();
}
private class InternalGump : Gump
{
private readonly BallotBox m_Box;
@ -259,7 +227,7 @@ namespace Server.Items
}
else
{
m_Box.Yes.Add(from);
m_Box.Add(m_Box._yes, from);
from.SendLocalizedMessage(500373); // Your vote has been registered.
}
}
@ -276,7 +244,7 @@ namespace Server.Items
}
else
{
m_Box.No.Add(from);
m_Box.Add(m_Box._no, from);
from.SendLocalizedMessage(500373); // Your vote has been registered.
}
}
@ -344,61 +312,25 @@ namespace Server.Items
}
}
public class BallotBoxAddon : BaseAddon
[Serializable(0)]
public partial class BallotBoxAddon : BaseAddon
{
public BallotBoxAddon()
{
AddComponent(new BallotBox(), 0, 0, 0);
}
public BallotBoxAddon(Serial serial) : base(serial)
{
}
public override BaseAddonDeed Deed => new BallotBoxDeed();
public override void Serialize(IGenericWriter writer)
{
base.Serialize(writer);
writer.WriteEncodedInt(0); // version
}
public override void Deserialize(IGenericReader reader)
{
base.Deserialize(reader);
var version = reader.ReadEncodedInt();
}
}
public class BallotBoxDeed : BaseAddonDeed
[Serializable(0)]
public partial class BallotBoxDeed : BaseAddonDeed
{
[Constructible]
public BallotBoxDeed()
{
}
public BallotBoxDeed(Serial serial) : base(serial)
{
}
public override BaseAddon Addon => new BallotBoxAddon();
public override int LabelNumber => 1044327; // ballot box
public override void Serialize(IGenericWriter writer)
{
base.Serialize(writer);
writer.WriteEncodedInt(0); // version
}
public override void Deserialize(IGenericReader reader)
{
base.Deserialize(reader);
var version = reader.ReadEncodedInt();
}
}
}

View file

@ -76,7 +76,7 @@ namespace Server.Items
Hue = CraftResources.GetHue(m_Resource);
InvalidateProperties();
((ISerializable)this).MarkDirty();
this.MarkDirty();
}
}
}

View file

@ -9,7 +9,7 @@ namespace Server.Items
protected FillableContent m_Content;
protected DateTime m_NextRespawnTime;
protected TimerExecutionToken _respawnTimerToken;
protected Timer _respawnTimer;
public FillableContainer(int itemID) : base(itemID) => Movable = false;
@ -96,7 +96,8 @@ namespace Server.Items
{
base.OnAfterDelete();
_respawnTimerToken.Cancel();
_respawnTimer?.Stop();
_respawnTimer = null;
}
public int GetItemsCount()
@ -118,24 +119,26 @@ namespace Server.Items
if (canSpawn)
{
if (!_respawnTimerToken.Running)
if (_respawnTimer?.Running != true)
{
var mins = Utility.RandomMinMax(MinRespawnMinutes, MaxRespawnMinutes);
var delay = TimeSpan.FromMinutes(mins);
m_NextRespawnTime = Core.Now + delay;
Timer.StartTimer(delay, Respawn, out _respawnTimerToken);
_respawnTimer = Timer.DelayCall(delay, Respawn);
}
}
else if (_respawnTimerToken.Running)
else
{
_respawnTimerToken.Cancel();
_respawnTimer?.Stop();
_respawnTimer = null;
}
}
public void Respawn()
{
_respawnTimerToken.Cancel();
_respawnTimer?.Stop();
_respawnTimer = null;
if (m_Content == null || Deleted)
{
@ -217,7 +220,7 @@ namespace Server.Items
{
var subItem = list[j];
if (!(subItem is Container) && subItem.StackWith(null, item, false))
if (subItem is not Container && subItem.StackWith(null, item, false))
{
break;
}
@ -238,7 +241,7 @@ namespace Server.Items
writer.Write((int)ContentType);
if (_respawnTimerToken.Running)
if (_respawnTimer?.Running == true)
{
writer.Write(true);
writer.WriteDeltaTime(m_NextRespawnTime);
@ -269,7 +272,7 @@ namespace Server.Items
m_NextRespawnTime = reader.ReadDeltaTime();
var delay = m_NextRespawnTime - Core.Now;
Timer.StartTimer(delay, Respawn, out _respawnTimerToken);
Timer.DelayCall(delay, Respawn);
}
else
{

View file

@ -275,7 +275,7 @@ namespace Server.Items
}
}
Timer.StartTimer(TimeSpan.FromMinutes(1.0), TimeSpan.FromMinutes(1.0), CheckEnd_OnTick);
Timer.DelayCall(TimeSpan.FromMinutes(1.0), TimeSpan.FromMinutes(1.0), CheckEnd_OnTick);
}
public bool ValidLocation() =>

View file

@ -70,6 +70,7 @@
"type": "System.Collections.Generic.List\u003CServer.Accounting.AccountComment\u003E",
"rule": "ListMigrationRule",
"ruleArguments": [
"",
"Server.Accounting.AccountComment",
"SerializationMethodSignatureMigrationRule"
]
@ -79,6 +80,7 @@
"type": "System.Collections.Generic.List\u003CServer.Accounting.AccountTag\u003E",
"rule": "ListMigrationRule",
"ruleArguments": [
"",
"Server.Accounting.AccountTag",
"SerializationMethodSignatureMigrationRule"
]

View file

@ -0,0 +1,35 @@
{
"version": 0,
"type": "Server.Items.BallotBox",
"properties": [
{
"name": "Topic",
"type": "string[]",
"rule": "ArrayMigrationRule",
"ruleArguments": [
"string",
"PrimitiveTypeMigrationRule"
]
},
{
"name": "Yes",
"type": "System.Collections.Generic.List\u003CServer.Mobile\u003E",
"rule": "ListMigrationRule",
"ruleArguments": [
"@Tidy",
"Server.Mobile",
"SerializableInterfaceMigrationRule"
]
},
{
"name": "No",
"type": "System.Collections.Generic.List\u003CServer.Mobile\u003E",
"rule": "ListMigrationRule",
"ruleArguments": [
"@Tidy",
"Server.Mobile",
"SerializableInterfaceMigrationRule"
]
}
]
}

View file

@ -0,0 +1,5 @@
{
"version": 0,
"type": "Server.Items.BallotBoxAddon",
"properties": []
}

View file

@ -0,0 +1,5 @@
{
"version": 0,
"type": "Server.Items.BallotBoxDeed",
"properties": []
}

View file

@ -7,6 +7,7 @@
"type": "System.Collections.Generic.List\u003CServer.Items.AddonComponent\u003E",
"rule": "ListMigrationRule",
"ruleArguments": [
"",
"Server.Items.AddonComponent",
"SerializableInterfaceMigrationRule"
]

View file

@ -29,7 +29,7 @@ namespace Server.Misc
list?.Add(this);
Timer.StartTimer(
Timer.DelayCall(
TimeSpan.FromSeconds((0.2 + Utility.RandomDouble() * 0.8) * interval.TotalSeconds),
interval,
OnTick

View file

@ -151,7 +151,7 @@ namespace Server.Mobiles
SetResistance(ResistanceType.Energy, 40, 60);
}
Timer.StartTimer(RemoveDisguise);
Timer.StartTimer(RemoveDisguise, out _disguiseTimerToken);
}
public void Disguise()
@ -208,6 +208,8 @@ namespace Server.Mobiles
public void RemoveDisguise()
{
_disguiseTimerToken.Cancel();
if (!IsBodyMod)
{
return;
@ -224,8 +226,6 @@ namespace Server.Mobiles
DeleteItemOnLayer(Layer.OuterTorso);
DeleteItemOnLayer(Layer.Shoes);
_disguiseTimerToken.Cancel();
}
public void DeleteItemOnLayer(Layer layer)

View file

@ -1067,7 +1067,7 @@ namespace Server.Mobiles
public static void UnequipMacro(Mobile m, List<Layer> layers)
{
if (m is PlayerMobile pm && pm.Backpack != null && pm.Alive)
if (m is PlayerMobile { Alive: true } pm && pm.Backpack != null)
{
var pack = pm.Backpack;
var eq = m.Items;

View file

@ -30,6 +30,18 @@ namespace Server.Mobiles
private readonly List<IBuyItemInfo> _buyInfo = new();
private readonly List<IShopSellInfo> _sellInfo = new();
public static void Initialize()
{
// This is technically more work than making timers, but we don't to deplete the timer pool immediately.
foreach (var m in World.Mobiles.Values)
{
if (m is BaseVendor bv)
{
bv.CheckMorph();
}
}
}
public BaseVendor(string title = null)
: base(AIType.AI_Vendor, FightMode.None, 2, 1, 0.5, 2)
{
@ -1360,8 +1372,6 @@ namespace Server.Mobiles
{
IsParagon = false;
}
Timer.StartTimer(CheckMorph);
}
public override void AddCustomContextEntries(Mobile from, List<ContextMenuEntry> list)

View file

@ -9,7 +9,7 @@ namespace Server.Multis
{
private TimeSpan m_DecayDelay;
private DateTime m_DecayTime;
private TimerExecutionToken _decayTimerToken;
private Timer _decayTimer;
private List<Item> m_Items;
private List<Mobile> m_Mobiles;
@ -55,6 +55,12 @@ namespace Server.Multis
{
}
public override void OnDelete()
{
_decayTimer?.Stop();
_decayTimer = null;
}
public virtual void RefreshDecay(bool setDecayTime)
{
if (Deleted)
@ -67,8 +73,8 @@ namespace Server.Multis
m_DecayTime = Core.Now + DecayDelay;
}
_decayTimerToken.Cancel();
Timer.StartTimer(DecayDelay, Delete, out _decayTimerToken);
_decayTimer?.Stop();
_decayTimer = Timer.DelayCall(DecayDelay, Delete);
}
public virtual void AddItem(Item item, int xOffset, int yOffset, int zOffset)

View file

@ -1197,7 +1197,7 @@ namespace Server.Multis
LockedDownFlag = 1;
SecureFlag = 2;
Timer.StartTimer(TimeSpan.FromMinutes(1.0), TimeSpan.FromMinutes(1.0), Decay_OnTick);
Timer.DelayCall(TimeSpan.FromMinutes(1.0), TimeSpan.FromMinutes(1.0), Decay_OnTick);
}
public virtual int GetAosCurLockdowns()

View file

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using Server.Collections;
using Server.Items;
using Server.Misc;
using Server.Mobiles;
@ -192,7 +193,6 @@ namespace Server.Spells.Fifth
private class InternalTimer : Timer
{
private static Queue<Mobile> m_Queue;
private readonly bool m_CanFit;
private readonly bool m_InLOS;
private readonly InternalItem m_Item;
@ -259,21 +259,21 @@ namespace Server.Spells.Fifth
)
);
using var queue = PooledRefQueue<Mobile>.Create();
foreach (var m in eable)
{
if (m.Z + 16 > m_Item.Z && m_Item.Z + 12 > m.Z && (!Core.AOS || m != caster) &&
SpellHelper.ValidIndirectTarget(caster, m) && caster.CanBeHarmful(m, false))
{
m_Queue ??= new Queue<Mobile>();
m_Queue.Enqueue(m);
queue.Enqueue(m);
}
}
eable.Free();
while (m_Queue?.Count > 0)
while (queue.Count > 0)
{
var m = m_Queue.Dequeue();
var m = queue.Dequeue();
caster.DoHarmful(m);

View file

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using Server.Collections;
using Server.Items;
using Server.Misc;
using Server.Mobiles;
@ -199,7 +200,6 @@ namespace Server.Spells.Fourth
private class InternalTimer : Timer
{
private static Queue<Mobile> m_Queue;
private readonly bool m_CanFit;
private readonly bool m_InLOS;
private readonly FireFieldItem m_Item;
@ -256,19 +256,19 @@ namespace Server.Spells.Fourth
return;
}
using var queue = PooledRefQueue<Mobile>.Create();
foreach (var m in m_Item.GetMobilesInRange(0))
{
if (m.Z + 16 > m_Item.Z && m_Item.Z + 12 > m.Z && (!Core.AOS || m != caster) &&
SpellHelper.ValidIndirectTarget(caster, m) && caster.CanBeHarmful(m, false))
{
m_Queue ??= new Queue<Mobile>();
m_Queue.Enqueue(m);
queue.Enqueue(m);
}
}
while (m_Queue?.Count > 0)
while (queue.Count > 0)
{
var m = m_Queue.Dequeue();
var m = queue.Dequeue();
if (m == null)
{
continue;