Skip to content

Commit bc941d5

Browse files
committed
Create EventEmitter.cs
1 parent 3bde268 commit bc941d5

1 file changed

Lines changed: 150 additions & 0 deletions

File tree

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
/*
2+
Copyright (C) 2024 DeathCradle
3+
4+
This file is part of Open Terraria API v3 (OTAPI)
5+
6+
This program is free software: you can redistribute it and/or modify
7+
it under the terms of the GNU General Public License as published by
8+
the Free Software Foundation, either version 3 of the License, or
9+
(at your option) any later version.
10+
11+
This program is distributed in the hope that it will be useful,
12+
but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
GNU General Public License for more details.
15+
16+
You should have received a copy of the GNU General Public License
17+
along with this program. If not, see <http://www.gnu.org/licenses/>.
18+
*/
19+
using Mono.Cecil;
20+
using Mono.Cecil.Cil;
21+
using System;
22+
using System.Linq;
23+
24+
namespace ModFramework;
25+
26+
[MonoMod.MonoModIgnore]
27+
public static class EventEmitter
28+
{
29+
/// <summary>
30+
/// Creates a new event based upon the source definition
31+
/// </summary>
32+
/// <param name="sourceDefinition">The method to base the even upon</param>
33+
/// <param name="containingType">The type to create the event in</param>
34+
/// <param name="eventArgsType">The event args to use</param>
35+
/// <param name="name">Optional desired name for the event</param>
36+
/// <returns>A tuple of the field and event definitions</returns>
37+
public static (FieldDefinition fieldDefinition, EventDefinition eventDefinition) CreateEvent(this MethodDefinition sourceDefinition, TypeDefinition containingType, TypeDefinition eventArgsType, string? name = null)
38+
{
39+
// Define the event backing field
40+
var fieldName = name ?? $"{sourceDefinition.Name}Event";
41+
FieldDefinition eventField = new(
42+
fieldName,
43+
FieldAttributes.Private | FieldAttributes.Static,
44+
new GenericInstanceType(sourceDefinition.Module.ImportReference(typeof(EventHandler<>)))
45+
{
46+
GenericArguments = { eventArgsType }
47+
}
48+
);
49+
containingType.Fields.Add(eventField);
50+
51+
// Define the event itself
52+
EventDefinition eventDefinition = new(
53+
fieldName,
54+
EventAttributes.None,
55+
eventField.FieldType
56+
);
57+
containingType.Events.Add(eventDefinition);
58+
59+
// Create the `add` method
60+
MethodDefinition addMethod = new(
61+
$"add_{fieldName}",
62+
MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.Static | MethodAttributes.SpecialName,
63+
containingType.Module.TypeSystem.Void
64+
);
65+
ParameterDefinition parameter = new("value", ParameterAttributes.None, eventField.FieldType);
66+
addMethod.Parameters.Add(parameter);
67+
var ilAdd = addMethod.Body.GetILProcessor();
68+
GenericInstanceMethod methodInterlockedCompareExchange = new(containingType.Module.ImportReference(typeof(System.Threading.Interlocked)
69+
.GetMethods()
70+
.Single(m => m.Name == "CompareExchange" && m.IsGenericMethodDefinition)));
71+
methodInterlockedCompareExchange.GenericArguments.Add(eventField.FieldType);
72+
73+
74+
VariableDefinition v0 = new(eventField.FieldType);
75+
VariableDefinition v1 = new(eventField.FieldType);
76+
VariableDefinition v2 = new(eventField.FieldType);
77+
addMethod.Body.Variables.Add(v0);
78+
addMethod.Body.Variables.Add(v1);
79+
addMethod.Body.Variables.Add(v2);
80+
81+
ilAdd.Emit(OpCodes.Ldsfld, eventField); // Load static field
82+
ilAdd.Emit(OpCodes.Stloc_0); // Store into local v0
83+
var loopStart = ilAdd.Create(OpCodes.Ldloc_0);
84+
ilAdd.Append(loopStart);
85+
ilAdd.Emit(OpCodes.Stloc_1); // Store into local v1
86+
ilAdd.Emit(OpCodes.Ldloc_1); // Load local v1
87+
ilAdd.Emit(OpCodes.Ldarg_0); // Load the parameter value
88+
ilAdd.Emit(OpCodes.Call, containingType.Module.ImportReference(typeof(Delegate).GetMethods().Single(x => x.Name == "Combine" && x.GetParameters().Length == 2)));
89+
ilAdd.Emit(OpCodes.Castclass, eventField.FieldType);
90+
ilAdd.Emit(OpCodes.Stloc_2); // Store into local v2
91+
ilAdd.Emit(OpCodes.Ldsflda, eventField);
92+
ilAdd.Emit(OpCodes.Ldloc_2);
93+
ilAdd.Emit(OpCodes.Ldloc_1);
94+
ilAdd.Emit(OpCodes.Call, methodInterlockedCompareExchange);
95+
ilAdd.Emit(OpCodes.Stloc_0); // Update v0
96+
ilAdd.Emit(OpCodes.Ldloc_0);
97+
ilAdd.Emit(OpCodes.Ldloc_1);
98+
ilAdd.Emit(OpCodes.Bne_Un_S, loopStart); // If not equal, loop back
99+
ilAdd.Emit(OpCodes.Ret);
100+
containingType.Methods.Add(addMethod);
101+
102+
// Create the `remove` method
103+
MethodDefinition removeMethod = new(
104+
$"remove_{fieldName}",
105+
MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.Static | MethodAttributes.SpecialName,
106+
containingType.Module.TypeSystem.Void
107+
);
108+
removeMethod.Parameters.Add(parameter);
109+
110+
v0 = new(eventField.FieldType);
111+
v1 = new(eventField.FieldType);
112+
v2 = new(eventField.FieldType);
113+
removeMethod.Body.Variables.Add(v0);
114+
removeMethod.Body.Variables.Add(v1);
115+
removeMethod.Body.Variables.Add(v2);
116+
117+
var ilRemove = removeMethod.Body.GetILProcessor();
118+
ilRemove.Emit(OpCodes.Ldsfld, eventField); // Load static field
119+
ilRemove.Emit(OpCodes.Stloc_0); // Store into local v0
120+
loopStart = ilRemove.Create(OpCodes.Ldloc_0);
121+
ilRemove.Append(loopStart);
122+
ilRemove.Emit(OpCodes.Stloc_1); // Store into local v1
123+
ilRemove.Emit(OpCodes.Ldloc_1); // Load local v1
124+
ilRemove.Emit(OpCodes.Ldarg_0); // Load the parameter value
125+
ilRemove.Emit(OpCodes.Call, containingType.Module.ImportReference(typeof(Delegate).GetMethod("Remove")));
126+
ilRemove.Emit(OpCodes.Castclass, eventField.FieldType);
127+
ilRemove.Emit(OpCodes.Stloc_2); // Store into local v2
128+
ilRemove.Emit(OpCodes.Ldsflda, eventField);
129+
ilRemove.Emit(OpCodes.Ldloc_2);
130+
ilRemove.Emit(OpCodes.Ldloc_1);
131+
ilRemove.Emit(OpCodes.Call, methodInterlockedCompareExchange);
132+
ilRemove.Emit(OpCodes.Stloc_0); // Update v0
133+
ilRemove.Emit(OpCodes.Ldloc_0);
134+
ilRemove.Emit(OpCodes.Ldloc_1);
135+
ilRemove.Emit(OpCodes.Bne_Un_S, loopStart); // If not equal, loop back
136+
ilRemove.Emit(OpCodes.Ret);
137+
containingType.Methods.Add(removeMethod);
138+
139+
// add compiler generated attribute
140+
addMethod.CustomAttributes.Add(new(containingType.Module.ImportReference(typeof(System.Runtime.CompilerServices.CompilerGeneratedAttribute).GetConstructor(Type.EmptyTypes))));
141+
removeMethod.CustomAttributes.Add(new(containingType.Module.ImportReference(typeof(System.Runtime.CompilerServices.CompilerGeneratedAttribute).GetConstructor(Type.EmptyTypes))));
142+
eventField.CustomAttributes.Add(new(containingType.Module.ImportReference(typeof(System.Runtime.CompilerServices.CompilerGeneratedAttribute).GetConstructor(Type.EmptyTypes))));
143+
144+
// Link the add/remove methods to the event
145+
eventDefinition.AddMethod = addMethod;
146+
eventDefinition.RemoveMethod = removeMethod;
147+
148+
return (eventField, eventDefinition);
149+
}
150+
}

0 commit comments

Comments
 (0)