Skip to content

Commit

Permalink
Modernize PlayerLoader, support custom ModPlayer hooks. (tModLoader#2645
Browse files Browse the repository at this point in the history
)

* Modernized PlayerLoader, ModPlayer is now a GlobalType

* Added PlayerLoader.AddModHook.

* Partial revert, IIndexed interface, repairs.

* player.(ModPlayers.array -> modPlayers)

* Undid unnecessary removal.

* Ordering, -trails.

* Player mod hook clearing.

* Make a HookList enumerator which works without Instanced<T>[]

* Add HookList.Enumerate variants for arrays, spans and lists

Co-authored-by: Chicken-Bones <[email protected]>
  • Loading branch information
Mirsario and Chicken-Bones authored Jun 30, 2022
1 parent 9ad6fc8 commit a21502d
Show file tree
Hide file tree
Showing 14 changed files with 391 additions and 301 deletions.
70 changes: 70 additions & 0 deletions patches/tModLoader/Terraria/ModLoader/Core/FilteredEnumerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
using System;
using System.Linq;
using System.Runtime.CompilerServices;

namespace Terraria.ModLoader.Core
{
// split into two classes, because the span version is slow. Keep an eye on https://github.com/dotnet/runtime/issues/68797 and check disassembly periodically
// or https://github.com/dotnet/runtime/issues/9977
// and my issue https://github.com/dotnet/runtime/issues/71510
public ref struct FilteredArrayEnumerator<T>
{
// T[] current produces much better code-gen than `ReadOnlySpan<T>`
private readonly T[] arr;
private readonly int[] inds;

private T current;
private int i;

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public FilteredArrayEnumerator(T[] arr, int[] inds) {
this.arr = arr;
this.inds = inds;
current = default;
i = 0;
}

public T Current => current;

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool MoveNext() {
if (i >= inds.Length)
return false;

current = arr[inds[i++]];
return true;
}

public FilteredArrayEnumerator<T> GetEnumerator() => this;
}

public ref struct FilteredSpanEnumerator<T>
{
private readonly ReadOnlySpan<T> arr;
private readonly int[] inds;

private T current;
private int i;

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public FilteredSpanEnumerator(ReadOnlySpan<T> arr, int[] inds) {
this.arr = arr;
this.inds = inds;
current = default;
i = 0;
}

public T Current => current;

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool MoveNext() {
if (i >= inds.Length)
return false;

current = arr[inds[i++]];
return true;
}

public FilteredSpanEnumerator<T> GetEnumerator() => this;
}
}
16 changes: 13 additions & 3 deletions patches/tModLoader/Terraria/ModLoader/Core/HookList.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

namespace Terraria.ModLoader.Core
{
public class HookList<T> where T : class
{
// Don't change a single line without performance testing and checking the disassembly. As of NET 5.0.0, this implementation is on par with hand-coding
// Don't change a single line without performance testing and checking the disassembly. As of NET 6.0.0, this implementation is on par with hand-coding C#
// Disassembly checked using Relyze Desktop 3.3.0
public ref struct InstanceEnumerator
{
Expand Down Expand Up @@ -79,8 +80,17 @@ public HookList(MethodInfo method) {
public InstanceEnumerator Enumerate(Instanced<T>[] instances)
=> new(instances, indices);

public void Update<U>(IList<U> instances) where U : GlobalType {
indices = instances.WhereMethodIsOverridden(method).Select(g => (int)g.index).ToArray();
public FilteredArrayEnumerator<T> Enumerate(T[] instances)
=> new(instances, indices);

public FilteredSpanEnumerator<T> Enumerate(ReadOnlySpan<T> instances)
=> new(instances, indices);

public FilteredSpanEnumerator<T> Enumerate(List<T> instances) =>
Enumerate(CollectionsMarshal.AsSpan(instances));

public void Update<U>(IList<U> instances) where U : IIndexed {
indices = instances.WhereMethodIsOverridden(method).Select(g => (int)g.Index).ToArray();
}

public static HookList<T> Create<F>(Expression<Func<T, F>> expr) where F : Delegate
Expand Down
2 changes: 1 addition & 1 deletion patches/tModLoader/Terraria/ModLoader/Core/LoaderUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ private static void InstantiateGlobals<TGlobal, TEntity>(TEntity entity, ReadOnl
int j = 0;
foreach (var g in set) {
if (g != null)
entityGlobals[j++] = new(g.index, g);
entityGlobals[j++] = new(g.Index, g);
}
}
else {
Expand Down
4 changes: 2 additions & 2 deletions patches/tModLoader/Terraria/ModLoader/GlobalItem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,14 @@ protected override void ValidateType() {
protected sealed override void Register() {
ModTypeLookup<GlobalItem>.Register(this);

index = (ushort)ItemLoader.globalItems.Count;
Index = (ushort)ItemLoader.globalItems.Count;

ItemLoader.globalItems.Add(this);
}

public sealed override void SetupContent() => SetStaticDefaults();

public GlobalItem Instance(Item item) => Instance(item.globalItems, index);
public GlobalItem Instance(Item item) => Instance(item.globalItems, Index);

/// <summary>
/// Allows you to set the properties of any and every item that gets created.
Expand Down
4 changes: 2 additions & 2 deletions patches/tModLoader/Terraria/ModLoader/GlobalNPC.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@ protected sealed override void Register() {

ModTypeLookup<GlobalNPC>.Register(this);

index = (ushort)NPCLoader.globalNPCs.Count;
Index = (ushort)NPCLoader.globalNPCs.Count;

NPCLoader.globalNPCs.Add(this);
}

public sealed override void SetupContent() => SetStaticDefaults();

public GlobalNPC Instance(NPC npc) => Instance(npc.globalNPCs, index);
public GlobalNPC Instance(NPC npc) => Instance(npc.globalNPCs, Index);

/// <summary>
/// Allows you to set the properties of any and every NPC that gets created.
Expand Down
4 changes: 2 additions & 2 deletions patches/tModLoader/Terraria/ModLoader/GlobalProjectile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,14 @@ protected sealed override void Register() {

ModTypeLookup<GlobalProjectile>.Register(this);

index = (ushort)ProjectileLoader.globalProjectiles.Count;
Index = (ushort)ProjectileLoader.globalProjectiles.Count;

ProjectileLoader.globalProjectiles.Add(this);
}

public sealed override void SetupContent() => SetStaticDefaults();

public GlobalProjectile Instance(Projectile projectile) => Instance(projectile.globalProjectiles, index);
public GlobalProjectile Instance(Projectile projectile) => Instance(projectile.globalProjectiles, Index);

/// <summary>
/// Allows you to set the properties of any and every projectile that gets created.
Expand Down
8 changes: 4 additions & 4 deletions patches/tModLoader/Terraria/ModLoader/GlobalType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@

namespace Terraria.ModLoader
{
public abstract class GlobalType : ModType
public abstract class GlobalType : ModType, IIndexed
{
internal ushort index;
public ushort Index { get; internal set; }

/// <summary>
/// Whether to create a new instance of this Global for every entity that exists.
Expand Down Expand Up @@ -54,7 +54,7 @@ public static bool TryGetGlobal<TGlobal, TResult>(Instanced<TGlobal>[] globals,
return false;
}

result = Instance(globals, baseInstance.index) as TResult;
result = Instance(globals, baseInstance.Index) as TResult;
return result != null;
}

Expand Down Expand Up @@ -125,7 +125,7 @@ public virtual TGlobal NewInstance(TEntity target) {

var inst = (TGlobal)Activator.CreateInstance(GetType());
inst.Mod = Mod;
inst.index = index;
inst.Index = Index;
return inst;
}
}
Expand Down
7 changes: 7 additions & 0 deletions patches/tModLoader/Terraria/ModLoader/IIndexed.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace Terraria.ModLoader
{
public interface IIndexed
{
ushort Index { get; }
}
}
37 changes: 16 additions & 21 deletions patches/tModLoader/Terraria/ModLoader/ItemLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ public static class ItemLoader
internal static readonly int[] vanillaWings = new int[Main.maxWings];

private static int nextItem = ItemID.Count;
private static Instanced<GlobalItem>[] globalItemsArray = new Instanced<GlobalItem>[0];

private static readonly List<HookList> hooks = new List<HookList>();
private static readonly List<HookList> modHooks = new List<HookList>();
Expand Down Expand Up @@ -122,10 +121,6 @@ internal static void ResizeArrays(bool unloading) {

FindVanillaWings();

globalItemsArray = globalItems
.Select(g => new Instanced<GlobalItem>(g.index, g))
.ToArray();

NetGlobals = globalItems.WhereMethodIsOverridden<GlobalItem, Action<Item, BinaryWriter>>(g => g.NetSend).ToArray();

foreach (var hook in hooks.Union(modHooks)) {
Expand Down Expand Up @@ -1135,7 +1130,7 @@ public static void UpdateArmorSet(Player player, Item head, Item body, Item legs
if (legs.ModItem != null && legs.ModItem.IsArmorSet(head, body, legs))
legs.ModItem.UpdateArmorSet(player);

foreach (GlobalItem globalItem in HookUpdateArmorSet.Enumerate(globalItemsArray)) {
foreach (GlobalItem globalItem in HookUpdateArmorSet.Enumerate(globalItems)) {
string set = globalItem.IsArmorSet(head, body, legs);
if (!string.IsNullOrEmpty(set))
globalItem.UpdateArmorSet(player, set);
Expand All @@ -1161,7 +1156,7 @@ public static void PreUpdateVanitySet(Player player) {
if (legTexture != null && legTexture.IsVanitySet(player.head, player.body, player.legs))
legTexture.PreUpdateVanitySet(player);

foreach (GlobalItem globalItem in HookPreUpdateVanitySet.Enumerate(globalItemsArray)) {
foreach (GlobalItem globalItem in HookPreUpdateVanitySet.Enumerate(globalItems)) {
string set = globalItem.IsVanitySet(player.head, player.body, player.legs);
if (!string.IsNullOrEmpty(set))
globalItem.PreUpdateVanitySet(player, set);
Expand All @@ -1187,7 +1182,7 @@ public static void UpdateVanitySet(Player player) {
if (legTexture != null && legTexture.IsVanitySet(player.head, player.body, player.legs))
legTexture.UpdateVanitySet(player);

foreach (GlobalItem globalItem in HookUpdateVanitySet.Enumerate(globalItemsArray)) {
foreach (GlobalItem globalItem in HookUpdateVanitySet.Enumerate(globalItems)) {
string set = globalItem.IsVanitySet(player.head, player.body, player.legs);
if (!string.IsNullOrEmpty(set))
globalItem.UpdateVanitySet(player, set);
Expand All @@ -1213,7 +1208,7 @@ public static void ArmorSetShadows(Player player) {
if (legTexture != null && legTexture.IsVanitySet(player.head, player.body, player.legs))
legTexture.ArmorSetShadows(player);

foreach (GlobalItem globalItem in HookArmorSetShadows.Enumerate(globalItemsArray)) {
foreach (GlobalItem globalItem in HookArmorSetShadows.Enumerate(globalItems)) {
string set = globalItem.IsVanitySet(player.head, player.body, player.legs);
if (!string.IsNullOrEmpty(set))
globalItem.ArmorSetShadows(player, set);
Expand All @@ -1231,7 +1226,7 @@ public static void SetMatch(int armorSlot, int type, bool male, ref int equipSlo

texture?.SetMatch(male, ref equipSlot, ref robes);

foreach (var g in HookSetMatch.Enumerate(globalItemsArray)) {
foreach (var g in HookSetMatch.Enumerate(globalItems)) {
g.SetMatch(armorSlot, type, male, ref equipSlot, ref robes);
}
}
Expand Down Expand Up @@ -1313,7 +1308,7 @@ public static void OpenBossBag(int type, Player player, ref int npc) {
/// </summary>
public static bool PreOpenVanillaBag(string context, Player player, int arg) {
bool result = true;
foreach (var g in HookPreOpenVanillaBag.Enumerate(globalItemsArray)) {
foreach (var g in HookPreOpenVanillaBag.Enumerate(globalItems)) {
result &= g.PreOpenVanillaBag(context, player, arg);
}

Expand All @@ -1331,7 +1326,7 @@ public static bool PreOpenVanillaBag(string context, Player player, int arg) {
/// Calls all GlobalItem.OpenVanillaBag hooks.
/// </summary>
public static void OpenVanillaBag(string context, Player player, int arg) {
foreach (var g in HookOpenVanillaBag.Enumerate(globalItemsArray)) {
foreach (var g in HookOpenVanillaBag.Enumerate(globalItems)) {
g.OpenVanillaBag(context, player, arg);
}
}
Expand All @@ -1346,7 +1341,7 @@ public static bool CanStack(Item item1, Item item2) {
if (item1.prefix != item2.prefix) // TML: #StackablePrefixWeapons
return false;

foreach (var g in HookCanStack.Enumerate(globalItemsArray)) {
foreach (var g in HookCanStack.Enumerate(globalItems)) {
if (!g.CanStack(item1, item2))
return false;
}
Expand All @@ -1360,7 +1355,7 @@ public static bool CanStack(Item item1, Item item2) {
/// Calls all GlobalItem.CanStackInWorld hooks until one returns false then ModItem.CanStackInWorld. Returns whether any of the hooks returned false.
/// </summary>
public static bool CanStackInWorld(Item item1, Item item2) {
foreach (var g in HookCanStackInWorld.Enumerate(globalItemsArray)) {
foreach (var g in HookCanStackInWorld.Enumerate(globalItems)) {
if (!g.CanStackInWorld(item1, item2))
return false;
}
Expand Down Expand Up @@ -1428,7 +1423,7 @@ public static void DrawArmorColor(EquipType type, int slot, Player drawPlayer, f
EquipTexture texture = EquipLoader.GetEquipTexture(type, slot);
texture?.DrawArmorColor(drawPlayer, shadow, ref color, ref glowMask, ref glowMaskColor);

foreach (var g in HookDrawArmorColor.Enumerate(globalItemsArray)) {
foreach (var g in HookDrawArmorColor.Enumerate(globalItems)) {
g.DrawArmorColor(type, slot, drawPlayer, shadow, ref color, ref glowMask, ref glowMaskColor);
}
}
Expand All @@ -1444,7 +1439,7 @@ public static void ArmorArmGlowMask(int slot, Player drawPlayer, float shadow, r

texture?.ArmorArmGlowMask(drawPlayer, shadow, ref glowMask, ref color);

foreach (var g in HookArmorArmGlowMask.Enumerate(globalItemsArray)) {
foreach (var g in HookArmorArmGlowMask.Enumerate(globalItems)) {
g.ArmorArmGlowMask(slot, drawPlayer, shadow, ref glowMask, ref color);
}
}
Expand Down Expand Up @@ -1537,7 +1532,7 @@ public static bool WingUpdate(Player player, bool inUse) {
EquipTexture texture = EquipLoader.GetEquipTexture(EquipType.Wings, player.wings);
bool? retVal = texture?.WingUpdate(player, inUse);

foreach (var g in HookWingUpdate.Enumerate(globalItemsArray)) {
foreach (var g in HookWingUpdate.Enumerate(globalItems)) {
retVal |= g.WingUpdate(player.wings, player, inUse);
}

Expand Down Expand Up @@ -1753,7 +1748,7 @@ public static void HoldoutOffset(float gravDir, int type, ref Vector2 offset) {
}
}

foreach (var g in HookHoldoutOffset.Enumerate(globalItemsArray)) {
foreach (var g in HookHoldoutOffset.Enumerate(globalItems)) {
Vector2? modOffset = g.HoldoutOffset(type);

if (modOffset.HasValue) {
Expand Down Expand Up @@ -1828,7 +1823,7 @@ private static bool CanAccessoryBeEquippedWith(Item equippedItem, Item incomingI
public static void ExtractinatorUse(ref int resultType, ref int resultStack, int extractType) {
GetItem(extractType)?.ExtractinatorUse(ref resultType, ref resultStack);

foreach (var g in HookExtractinatorUse.Enumerate(globalItemsArray)) {
foreach (var g in HookExtractinatorUse.Enumerate(globalItems)) {
g.ExtractinatorUse(extractType, ref resultType, ref resultStack);
}
}
Expand All @@ -1851,7 +1846,7 @@ public static void IsAnglerQuestAvailable(int itemID, ref bool notAvailable) {
if (modItem != null)
notAvailable |= !modItem.IsAnglerQuestAvailable();

foreach (var g in HookIsAnglerQuestAvailable.Enumerate(globalItemsArray)) {
foreach (var g in HookIsAnglerQuestAvailable.Enumerate(globalItems)) {
notAvailable |= !g.IsAnglerQuestAvailable(itemID);
}
}
Expand All @@ -1864,7 +1859,7 @@ public static string AnglerChat(int type) {
string catchLocation = "";
GetItem(type)?.AnglerQuestChat(ref chat, ref catchLocation);

foreach (var g in HookAnglerChat.Enumerate(globalItemsArray)) {
foreach (var g in HookAnglerChat.Enumerate(globalItems)) {
g.AnglerChat(type, ref chat, ref catchLocation);
}

Expand Down
10 changes: 6 additions & 4 deletions patches/tModLoader/Terraria/ModLoader/ModPlayer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,22 @@ namespace Terraria.ModLoader
/// <summary>
/// A ModPlayer instance represents an extension of a Player instance. You can store fields in the ModPlayer classes, much like how the Player class abuses field usage, to keep track of mod-specific information on the player that a ModPlayer instance represents. It also contains hooks to insert your code into the Player class.
/// </summary>
public abstract class ModPlayer : ModType<Player, ModPlayer>
public abstract class ModPlayer : ModType<Player, ModPlayer>, IIndexed
{
public ushort Index { get; internal set; }

/// <summary>
/// The Player instance that this ModPlayer instance is attached to.
/// </summary>
public Player Player => Entity;

internal ushort index;

protected override Player CreateTemplateEntity() => null;

public override ModPlayer NewInstance(Player entity) {
var inst = base.NewInstance(entity);
inst.index = index;

inst.Index = Index;

return inst;
}

Expand Down
Loading

0 comments on commit a21502d

Please sign in to comment.