Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions source/E2E.Tests/Environment/E2ETestEnvironment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ public MethodInfo GetArrayChangeIntercept(MemberInfo member)
Assert.True(fieldInfo.GetValue(serverInstance).Equals(defaultVal), $"Expected: {defaultVal} Actual: {fieldInfo.GetValue(serverInstance)}");
else
Assert.True(JsonConvert.SerializeObject(fieldInfo.GetValue(serverInstance)).Equals(JsonConvert.SerializeObject(defaultVal)), $"Expected: {JsonConvert.SerializeObject(defaultVal)} Actual: {JsonConvert.SerializeObject(fieldInfo.GetValue(serverInstance))}");
intercept.Invoke(null, new object[] { serverInstance, value, fieldName });
intercept.Invoke(null, new object[] { serverInstance, value });
Assert.True(value.Equals(fieldInfo.GetValue(serverInstance)), $"Expected: {value} Actual: {fieldInfo.GetValue(serverInstance)}");
});

Expand Down Expand Up @@ -286,7 +286,7 @@ public void AssertReferenceField<TInstance, TField>(string fieldName, string? in
Assert.True(Server.ObjectManager.TryGetObject<TInstance>(instanceId, out var serverInstance));
Assert.True(Server.ObjectManager.TryGetObject<TField>(referenceId, out var serverFieldInstance));
Assert.Equal(defaultValue ?? fieldInfo.GetUnderlyingType().GetDefaultValue(), fieldInfo.GetValue(serverInstance));
intercept.Invoke(null, new object[] { serverInstance, serverFieldInstance, fieldName });
intercept.Invoke(null, new object[] { serverInstance, serverFieldInstance });
Assert.True(serverFieldInstance.Equals(fieldInfo.GetValue(serverInstance)));// TODO: re add error, $"Expected: {serverFieldInstance} Actual: {fieldInfo.GetValue(serverInstance)}");
Assert.NotNull(serverFieldInstance);
});
Expand Down
28 changes: 18 additions & 10 deletions source/E2E.Tests/Services/CraftingService/CraftingSyncTests.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
using Common.Util;
using E2E.Tests.Environment;
using E2E.Tests.Environment;
using HarmonyLib;
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;
using System.Reflection;
using TaleWorlds.CampaignSystem;
using TaleWorlds.CampaignSystem.Settlements.Buildings;
using TaleWorlds.CampaignSystem.Siege;
using TaleWorlds.Core;
using TaleWorlds.Localization;
using Xunit.Abstractions;
Expand Down Expand Up @@ -39,14 +35,13 @@ public void ServerCreateCrafting_SyncAllClients()

var currentHistoryIntercept = TestEnvironment.GetIntercept(currentHistoryIndexField);
var craftingItemObjectIntercept = TestEnvironment.GetIntercept(craftedItemObjectField);


server.Call(() =>
{
CraftingTemplate craftingTemplate = new CraftingTemplate();
CultureObject cultureObject = new CultureObject();
Crafting crafting = new Crafting(craftingTemplate, cultureObject, new TextObject("test"));


Assert.True(server.ObjectManager.TryGetId(crafting, out craftingId));
});
Expand Down Expand Up @@ -78,21 +73,34 @@ public void ServerCreateCrafting_SyncAllClients()
img.AddItemModifier(modifier);
serverCrafting.CurrentItemModifierGroup = img;
serverCrafting.CraftedWeaponName = new TextObject("craftedWeapon");
WeaponDesign weaponDesign = ObjectHelper.SkipConstructor<WeaponDesign>();

var weaponDesign = new WeaponDesign(
new CraftingTemplate(),
new TextObject("Excaliber"),
new WeaponDesignElement[] {
new WeaponDesignElement(new CraftingPiece())
});
serverCrafting.CurrentWeaponDesign = weaponDesign;
},
new MethodInfo[]
{
AccessTools.Method(typeof(WeaponDesign), nameof(WeaponDesign.CalculatePivotDistances)),
AccessTools.Method(typeof(WeaponDesign), nameof(WeaponDesign.CalculateHolsterShiftAmount))
});

server.ObjectManager.TryGetId(serverCrafting._craftedItemObject, out string serverObjectId);
server.ObjectManager.TryGetId(serverCrafting.CurrentWeaponDesign, out string serverWeaponDesignId);

foreach (var client in TestEnvironment.Clients)
{
Assert.True(client.ObjectManager.TryGetObject(craftingId, out Crafting clientCrafting));
Assert.Equal(serverCrafting._currentHistoryIndex, clientCrafting._currentHistoryIndex);

client.ObjectManager.TryGetId(clientCrafting._craftedItemObject, out string clientObjectId);
client.ObjectManager.TryGetId(clientCrafting.CurrentWeaponDesign, out string clientWeaponDesignId);

Assert.Equal(serverObjectId, clientObjectId);
Assert.Equal(serverCrafting.CurrentWeaponDesign, clientCrafting.CurrentWeaponDesign);
Assert.Equal(serverWeaponDesignId, clientWeaponDesignId);
Assert.Equal(serverCrafting.CurrentItemModifierGroup.ItemModifiers.Count, clientCrafting.CurrentItemModifierGroup.ItemModifiers.Count);
Assert.Equal(serverCrafting.CraftedWeaponName.ToString(), clientCrafting.CraftedWeaponName.ToString());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public SyntaxTree Build(IEnumerable<string> assemblies)
}
}

var assemblyInfoTemplate = TemplateParser.Parse("DynamicAssemblyInfoTemplate", new
var assemblyInfoTemplate = TemplateParser.Parse("DynamicAssemblyInfoTemplate", new
{
Assemblies = ignoreCheckAccessAssemblies.Distinct().Select(a => a.GetName().Name).Concat(assemblies)
});
Expand Down
17 changes: 8 additions & 9 deletions source/GameInterface/DynamicSync/Builders/DynamicSyncBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;

namespace GameInterface.DynamicSync.Builders;

Expand Down Expand Up @@ -38,22 +39,20 @@ public Assembly Build()
{
assemblies.Add(typeof(System.Collections.ArrayList).Assembly);
assemblies.Add(typeof(System.Runtime.AssemblyTargetedPatchBandAttribute).Assembly);
assemblies.Add(typeof(Enumerable).Assembly);
assemblies.Add(typeof(Queue<>).Assembly);
assemblies.Add(typeof(Console).Assembly);
}
else
{
assemblies.Add(typeof(Enumerable).Assembly);
assemblies.Add(typeof(Queue<>).Assembly);
assemblies.Add(Assembly.Load("System.Runtime"));
assemblies.Add(Assembly.Load("System.Private.CoreLib"));
assemblies.Add(Assembly.Load("System.Collections"));
assemblies.Add(typeof(Console).Assembly);

}


assemblies.Add(typeof(Enumerable).Assembly);
assemblies.Add(typeof(Queue<>).Assembly);
assemblies.Add(typeof(Console).Assembly);
assemblies.Add(Assembly.Load("System.Reflection.Primitives"));
assemblies.Add(Assembly.Load("System.Collections.Concurrent"));
assemblies.Add(Assembly.Load("System.Numerics.Vectors"));

foreach (var asm in Assembly.GetExecutingAssembly().GetReferencedAssemblies())
{
Expand Down Expand Up @@ -83,7 +82,7 @@ public Assembly Build()
topLevelBinderFlagsProperty.SetValue(compilationOptions, (uint)1 << 22);

var dynamicAssembly = CSharpCompilation.Create(
"DynamicSync.dll",
"DynamicSync",
syntaxTrees: syntaxTrees,
references:
assemblies.Select(a => a.Location).Distinct().Select(a => MetadataReference.CreateFromFile(a)),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,15 @@ public DynamicSyncBuilderBase(DynamicSyncRegistry dynamicSyncRegistry)
$"{serializer.Deserialize.DeclaringType.Namespace}.{serializer.Deserialize.DeclaringType.Name}.{serializer.Deserialize.Name}");
}

protected int GetReadonlyFieldSetter(FieldInfo info)
protected int GetReadOnlyFieldSetter(FieldInfo info)
{
var method = new DynamicMethod(
name: info.DeclaringType.Name + info.Name + "Setter",
returnType: null,
parameterTypes: new[] { info.DeclaringType, info.FieldType },
restrictedSkipVisibility: true
);

var gen = method.GetILGenerator();
gen.Emit(OpCodes.Ldarg_0);
gen.Emit(OpCodes.Ldarg_1);
Expand All @@ -45,7 +46,7 @@ protected int GetReadonlyFieldSetter(FieldInfo info)
// TODO: find a way to store the delegate without needing the DynamicInvoke if performance is an issue
var del = method.CreateDelegate(actionType);
var action = (object a, object b) => { del.DynamicInvoke(a, b); };
return dynamicSyncRegistry.AddReadonlySetter(action);
return dynamicSyncRegistry.AddReadOnlySetter(action);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ private object GetTemplateData(FieldInfo fieldInfo)
SerializeMethod = serializers.serialize,
DeserializeMethod = serializers.deserialize,
ReadOnly = fieldInfo.IsInitOnly,
ReadOnlySetterIndex = fieldInfo.IsInitOnly ? GetReadonlyFieldSetter(fieldInfo) : (int?)null
ReadOnlySetterIndex = fieldInfo.IsInitOnly ? GetReadOnlyFieldSetter(fieldInfo) : (int?)null
};
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ private object GetTemplateData(FieldInfo fieldInfo)
SerializeMethod = serializerNames.serialize,
DeserializeMethod = serializerNames.deserialize,
ReadOnly = fieldInfo.IsInitOnly,
ReadOnlySetterIndex = fieldInfo.IsInitOnly ? GetReadonlyFieldSetter(fieldInfo) : (int?)null
ReadOnlySetterIndex = fieldInfo.IsInitOnly ? GetReadOnlyFieldSetter(fieldInfo) : (int?)null
};
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ private object GetTemplateData(FieldInfo fieldInfo)
SerializeMethod = serializers.serialize,
DeserializeMethod = serializers.deserialize,
ReadOnly = fieldInfo.IsInitOnly,
ReadOnlySetterIndex = fieldInfo.IsInitOnly ? GetReadonlyFieldSetter(fieldInfo) : (int?)null
ReadOnlySetterIndex = fieldInfo.IsInitOnly ? GetReadOnlyFieldSetter(fieldInfo) : (int?)null
};
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ private object GetTemplateData(FieldInfo fieldInfo)
SerializeMethod = serializers.serialize,
DeserializeMethod = serializers.deserialize,
ReadOnly = fieldInfo.IsInitOnly,
ReadOnlySetterIndex = fieldInfo.IsInitOnly ? GetReadonlyFieldSetter(fieldInfo) : (int?)null
ReadOnlySetterIndex = fieldInfo.IsInitOnly ? GetReadOnlyFieldSetter(fieldInfo) : (int?)null
};
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ private object GetTemplateData(FieldInfo fieldInfo)
SerializeMethod = serializers.serialize,
DeserializeMethod = serializers.deserialize,
ReadOnly = fieldInfo.IsInitOnly,
ReadOnlySetterIndex = fieldInfo.IsInitOnly ? GetReadonlyFieldSetter(fieldInfo) : (int?)null
ReadOnlySetterIndex = fieldInfo.IsInitOnly ? GetReadOnlyFieldSetter(fieldInfo) : (int?)null
};
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ public List<SyntaxTree> Build(Type declaringType, DynamicSyncRegistryItem dynami
Transpilers = transpilers
});

DynamicSyncConfiguration.ExportFile($"{declaringType.Name}/{declaringType.Name}_DynamicPatch.cs", patchTemplate);
DynamicSyncConfiguration.ExportFile($"{declaringType.Name}/{declaringType.Name}_DynamicPatches.cs", patchTemplate);

var handlerTemplate = TemplateParser.Parse("Handlers.DynamicHandlerTemplate", new
{
Expand Down
2 changes: 1 addition & 1 deletion source/GameInterface/DynamicSync/DynamicHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public DynamicHandler(IMessageBroker messageBroker, IObjectManager objectManager
public void RegisterHandler(Type handlerType)
{
if(!handlers.Any(h => h.GetType() == handlerType))
handlers.Add((IHandler)Activator.CreateInstance(handlerType, new object[] { messageBroker, objectManager, network, dynamicSyncRegistry }));
handlers.Add((IHandler)Activator.CreateInstance(handlerType, new object[] { messageBroker, objectManager, network, dynamicSyncRegistry }));
}

public void Dispose()
Expand Down
6 changes: 3 additions & 3 deletions source/GameInterface/DynamicSync/DynamicSyncPatcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,10 @@ public void BindHandlers(Assembly assembly)
return false;
}
}));

var handlers = assembly.GetTypes()
.Where(type => {
return type.Name.EndsWith("_Handler");
}).ToList();
.Where(type => type.Name.EndsWith("_Handler"));

foreach (var handler in handlers)
{
dynamicHandler.RegisterHandler(handler);
Expand Down
4 changes: 2 additions & 2 deletions source/GameInterface/DynamicSync/DynamicSyncRegistry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public bool AddTargetMethod(Type type, MethodInfo methodInfo)
/// <typeparam name="TargetType"></typeparam>
/// <param name="serialize"></param>
/// <param name="deserialize"></param>
public void AddSerializer<TargetType>(Func<TargetType, byte[]> serialize, Func<byte[], TargetType, TargetType>deserialize)
public void AddSerializer<TargetType>(Func<TargetType, byte[]> serialize, Func<byte[], TargetType, TargetType> deserialize)
{
DynamicSyncRegistrySerializer serializer = new DynamicSyncRegistrySerializer
{
Expand All @@ -73,7 +73,7 @@ public void AddSerializer<TargetType>(Func<TargetType, byte[]> serialize, Func<b
Serializers.Add(typeof(TargetType), serializer);
}

public int AddReadonlySetter(Action<object, object> accessor)
public int AddReadOnlySetter(Action<object, object> accessor)
{
ReadonlySetters.Add(accessor);

Expand Down
105 changes: 105 additions & 0 deletions source/GameInterface/DynamicSync/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
# Dynamic Sync

1. [Collects]() all classes that inherit [IDynamicSync]()
2. Each field/propery/collection added from the IDynamicSync class is stored in the [DynamicSyncRegistry]()


There are 4 different types of data
1. Fields
2. Properties
3. Lists
4.
3. After all IDynamicSync classes are instantiated, DynamicSyncPatchBuilder creates the following

1. Handlers
2. Messages
3. Patches


## Syncing Fields

```CS
public class MyClass
{
public int MyInt = 5;

public void UpdateMyValue(int newValue)
{
MyInt = newValue
}
}
```

To automatically sync a field
```CS
public class MyDynamicSyncClass : IDynamicSync
{
public MyDynamicSyncClass(DynamicSyncRegistry dynamicSyncRegistry)
{
dynamicSyncRegistry.AddField(typeof(MyClass), nameof(MyClass.MyInt));
}
}
```


**Warning:** Patches generated are only intercepting setting the value in the containing class

For example, the following will automatically be updated across the network
```CS
public class MyClass
{
public int MyInt = 5;

public void UpdateMyValue(int newValue)
{
MyInt = newValue
}
}
```

However, if used by an external class. The value will not be updated automatically
```CS
public class SomeOtherClass
{
public MyClass Instance;

public void UpdateValue(int newValue)
{
Instance.MyInt = newValue
}
}
```

To update automatically from an external class, use AddTargetMethod in the IDynamicSync class

```CS
var method = AccessTools.Method(typeof(SomeOtherClass), nameof(SomeOtherClass.UpdateValue));

dynamicSyncRegistry.AddTargetMethod(typeof(SomeOtherClass), method);
```

## Properties
```CS
public class MyClass
{
public int MyIntProperty { get; set; }
}
```

To automatically sync a property
```CS
public class MyDynamicSyncClass : IDynamicSync
{
public MyDynamicSyncClass(DynamicSyncRegistry dynamicSyncRegistry)
{
dynamicSyncRegistry.AddProperty(typeof(MyClass), nameof(MyClass.MyIntProperty));
}
}
```

## Collections

### Array
### List
### MBList
### Queue
Loading
Loading