updated: hot reload to 1.13.7
This commit is contained in:
@@ -1,11 +1,12 @@
|
||||
#if ENABLE_MONO && (DEVELOPMENT_BUILD || UNITY_EDITOR)
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using SingularityGroup.HotReload.DTO;
|
||||
|
||||
namespace SingularityGroup.HotReload.Burst {
|
||||
public static class JobHotReloadUtility {
|
||||
public static void HotReloadBurstCompiledJobs(CodePatch patch, Module module) {
|
||||
JobPatchUtility.PatchBurstCompiledJobs(patch, module, unityMajorVersion:
|
||||
public static void HotReloadBurstCompiledJobs(SUnityJob jobData, Type proxyJobType) {
|
||||
JobPatchUtility.PatchBurstCompiledJobs(jobData, proxyJobType, unityMajorVersion:
|
||||
#if UNITY_2022_2_OR_NEWER
|
||||
2022
|
||||
#elif UNITY_2021_3_OR_NEWER
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
@@ -13,8 +14,13 @@ using JetBrains.Annotations;
|
||||
using SingularityGroup.HotReload.Burst;
|
||||
using SingularityGroup.HotReload.HarmonyLib;
|
||||
using SingularityGroup.HotReload.JsonConverters;
|
||||
using SingularityGroup.HotReload.MonoMod.Utils;
|
||||
using SingularityGroup.HotReload.Newtonsoft.Json;
|
||||
using SingularityGroup.HotReload.RuntimeDependencies;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
using UnityEditorInternal;
|
||||
#endif
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
|
||||
@@ -24,8 +30,23 @@ namespace SingularityGroup.HotReload {
|
||||
class RegisterPatchesResult {
|
||||
// note: doesn't include removals and method definition changes (e.g. renames)
|
||||
public readonly List<MethodPatch> patchedMethods = new List<MethodPatch>();
|
||||
public List<SField> addedFields = new List<SField>();
|
||||
public readonly List<SMethod> patchedSMethods = new List<SMethod>();
|
||||
public bool inspectorModified;
|
||||
public readonly List<Tuple<SMethod, string>> patchFailures = new List<Tuple<SMethod, string>>();
|
||||
public readonly List<string> patchExceptions = new List<string>();
|
||||
}
|
||||
|
||||
class FieldHandler {
|
||||
public readonly Action<Type, FieldInfo> storeField;
|
||||
public readonly Action<Type, FieldInfo, FieldInfo> registerInspectorFieldAttributes;
|
||||
public readonly Func<Type, string, bool> hideField;
|
||||
|
||||
public FieldHandler(Action<Type, FieldInfo> storeField, Func<Type, string, bool> hideField, Action<Type, FieldInfo, FieldInfo> registerInspectorFieldAttributes) {
|
||||
this.storeField = storeField;
|
||||
this.hideField = hideField;
|
||||
this.registerInspectorFieldAttributes = registerInspectorFieldAttributes;
|
||||
}
|
||||
}
|
||||
|
||||
class CodePatcher {
|
||||
@@ -42,6 +63,8 @@ namespace SingularityGroup.HotReload {
|
||||
string[] assemblySearchPaths;
|
||||
SymbolResolver symbolResolver;
|
||||
readonly string tmpDir;
|
||||
public FieldHandler fieldHandler;
|
||||
public bool debuggerCompatibilityEnabled;
|
||||
|
||||
CodePatcher() {
|
||||
pendingPatches = new List<MethodPatchResponse>();
|
||||
@@ -108,6 +131,26 @@ namespace SingularityGroup.HotReload {
|
||||
if (seenResponses.Contains(response.id)) {
|
||||
continue;
|
||||
}
|
||||
foreach (var patch in response.patches) {
|
||||
var asm = Assembly.Load(patch.patchAssembly, patch.patchPdb);
|
||||
SymbolResolver.AddAssembly(asm);
|
||||
}
|
||||
HandleRemovedUnityMethods(response.removedMethod);
|
||||
#if UNITY_EDITOR
|
||||
HandleAlteredFields(response.id, result, response.alteredFields);
|
||||
#endif
|
||||
// needs to come before RegisterNewFieldInitializers
|
||||
RegisterNewFieldDefinitions(response);
|
||||
// Note: order is important here. Reshaped fields require new field initializers to be added
|
||||
// because the old initializers must override new initilaizers for existing holders.
|
||||
// so that the initializer is not invoked twice
|
||||
RegisterNewFieldInitializers(response);
|
||||
HandleReshapedFields(response);
|
||||
RemoveOldFieldInitializers(response);
|
||||
#if UNITY_EDITOR
|
||||
RegisterInspectorFieldAttributes(result, response);
|
||||
#endif
|
||||
|
||||
HandleMethodPatchResponse(response, result);
|
||||
patchHistory.Add(response);
|
||||
|
||||
@@ -152,11 +195,8 @@ namespace SingularityGroup.HotReload {
|
||||
|
||||
foreach(var patch in response.patches) {
|
||||
try {
|
||||
var asm = Assembly.Load(patch.patchAssembly, patch.patchPdb);
|
||||
|
||||
var module = asm.GetLoadedModules()[0];
|
||||
foreach(var sMethod in patch.newMethods) {
|
||||
var newMethod = module.ResolveMethod(sMethod.metadataToken);
|
||||
var newMethod = SymbolResolver.Resolve(sMethod);
|
||||
try {
|
||||
UnityEventHelper.EnsureUnityEventMethod(newMethod);
|
||||
} catch(Exception ex) {
|
||||
@@ -171,43 +211,273 @@ namespace SingularityGroup.HotReload {
|
||||
}
|
||||
}
|
||||
|
||||
symbolResolver.AddAssembly(asm);
|
||||
for (int i = 0; i < patch.modifiedMethods.Length; i++) {
|
||||
var sOriginalMethod = patch.modifiedMethods[i];
|
||||
var sPatchMethod = patch.patchMethods[i];
|
||||
var err = PatchMethod(module: module, sOriginalMethod: sOriginalMethod, sPatchMethod: sPatchMethod, containsBurstJobs: patch.unityJobs.Length > 0, patchesResult: result);
|
||||
var err = PatchMethod(response.id, sOriginalMethod: sOriginalMethod, sPatchMethod: sPatchMethod, containsBurstJobs: patch.unityJobs.Length > 0, patchesResult: result);
|
||||
if (!string.IsNullOrEmpty(err)) {
|
||||
result.patchFailures.Add(Tuple.Create(sOriginalMethod, err));
|
||||
}
|
||||
}
|
||||
JobHotReloadUtility.HotReloadBurstCompiledJobs(patch, module);
|
||||
foreach (var job in patch.unityJobs) {
|
||||
var type = SymbolResolver.Resolve(new SType(patch.assemblyName, job.jobKind.ToString(), job.metadataToken));
|
||||
JobHotReloadUtility.HotReloadBurstCompiledJobs(job, type);
|
||||
}
|
||||
#if UNITY_EDITOR
|
||||
HandleNewFields(patch.patchId, result, patch.newFields);
|
||||
#endif
|
||||
} catch (Exception ex) {
|
||||
RequestHelper.RequestEditorEventWithRetry(new Stat(StatSource.Client, StatLevel.Error, StatFeature.Patching, StatEventType.Exception), new EditorExtraData {
|
||||
{ StatKey.PatchId, patch.patchId },
|
||||
{ StatKey.Detailed_Exception, ex.ToString() },
|
||||
}).Forget();
|
||||
result.patchExceptions.Add($"Edit requires full recompile to apply: Encountered exception when applying a patch.\nCommon causes: editing code that failed to patch previously, an unsupported change, or a real bug in Hot Reload.\nIf you think this is a bug, please report the issue on Discord and include a code-snippet before/after.\nException: {ex}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HandleRemovedUnityMethods(SMethod[] removedMethods) {
|
||||
if (removedMethods == null) {
|
||||
return;
|
||||
}
|
||||
foreach(var sMethod in removedMethods) {
|
||||
try {
|
||||
var oldMethod = SymbolResolver.Resolve(sMethod);
|
||||
UnityEventHelper.RemoveUnityEventMethod(oldMethod);
|
||||
} catch (SymbolResolvingFailedException) {
|
||||
// ignore, not a unity event method if can't resolve
|
||||
} catch(Exception ex) {
|
||||
Log.Warning("Failed to apply patch with id: {0}\n{1}", patch.patchId, ex);
|
||||
Log.Warning("Encountered exception in RemoveUnityEventMethod: {0} {1}", ex.GetType().Name, ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Important: must come before applying any patches
|
||||
void RegisterNewFieldInitializers(MethodPatchResponse resp) {
|
||||
for (var i = 0; i < resp.addedFieldInitializerFields.Length; i++) {
|
||||
var sField = resp.addedFieldInitializerFields[i];
|
||||
var sMethod = resp.addedFieldInitializerInitializers[i];
|
||||
try {
|
||||
var declaringType = SymbolResolver.Resolve(sField.declaringType);
|
||||
var method = SymbolResolver.Resolve(sMethod);
|
||||
if (!(method is MethodInfo initializer)) {
|
||||
Log.Warning($"Failed registering initializer for field {sField.fieldName} in {sField.declaringType.typeName}. Field value might not be initialized correctly. Invalid method.");
|
||||
continue;
|
||||
}
|
||||
// We infer if the field is static by the number of parameters the method has
|
||||
// because sField is old field
|
||||
var isStatic = initializer.GetParameters().Length == 0;
|
||||
MethodUtils.DisableVisibilityChecks(initializer);
|
||||
// Initializer return type is used in place of fieldType because latter might be point to old field if the type changed
|
||||
FieldInitializerRegister.RegisterInitializer(declaringType, sField.fieldName, initializer.ReturnType, initializer, isStatic);
|
||||
|
||||
} catch (Exception e) {
|
||||
RequestHelper.RequestEditorEventWithRetry(new Stat(StatSource.Client, StatLevel.Error, StatFeature.Patching, StatEventType.RegisterFieldInitializer), new EditorExtraData {
|
||||
{ StatKey.PatchId, resp.id },
|
||||
{ StatKey.Detailed_Exception, e.ToString() },
|
||||
}).Forget();
|
||||
Log.Warning($"Failed registering initializer for field {sField.fieldName} in {sField.declaringType.typeName}. Field value might not be initialized correctly. Exception: {e.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RegisterNewFieldDefinitions(MethodPatchResponse resp) {
|
||||
foreach (var sField in resp.newFieldDefinitions) {
|
||||
try {
|
||||
var declaringType = SymbolResolver.Resolve(sField.declaringType);
|
||||
var fieldType = SymbolResolver.Resolve(sField).FieldType;
|
||||
FieldResolver.RegisterFieldType(declaringType, sField.fieldName, fieldType);
|
||||
} catch (Exception e) {
|
||||
RequestHelper.RequestEditorEventWithRetry(new Stat(StatSource.Client, StatLevel.Error, StatFeature.Patching, StatEventType.RegisterFieldDefinition), new EditorExtraData {
|
||||
{ StatKey.PatchId, resp.id },
|
||||
{ StatKey.Detailed_Exception, e.ToString() },
|
||||
}).Forget();
|
||||
Log.Warning($"Failed registering new field definitions for field {sField.fieldName} in {sField.declaringType.typeName}. Exception: {e.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Important: must come before applying any patches
|
||||
// Note: server might decide not to report removed field initializer at all if it can handle it
|
||||
void RemoveOldFieldInitializers(MethodPatchResponse resp) {
|
||||
foreach (var sField in resp.removedFieldInitializers) {
|
||||
try {
|
||||
var declaringType = SymbolResolver.Resolve(sField.declaringType);
|
||||
var fieldType = SymbolResolver.Resolve(sField.declaringType);
|
||||
FieldInitializerRegister.UnregisterInitializer(declaringType, sField.fieldName, fieldType, sField.isStatic);
|
||||
} catch (Exception e) {
|
||||
RequestHelper.RequestEditorEventWithRetry(new Stat(StatSource.Client, StatLevel.Error, StatFeature.Patching, StatEventType.UnregisterFieldInitializer), new EditorExtraData {
|
||||
{ StatKey.PatchId, resp.id },
|
||||
{ StatKey.Detailed_Exception, e.ToString() },
|
||||
}).Forget();
|
||||
Log.Warning($"Failed removing initializer for field {sField.fieldName} in {sField.declaringType.typeName}. Field value might not be initialized correctly. Exception: {e.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Important: must come before applying any patches
|
||||
// Should also come after RegisterNewFieldInitializers so that new initializers are not invoked for existing objects
|
||||
internal void HandleReshapedFields(MethodPatchResponse resp) {
|
||||
foreach(var patch in resp.patches) {
|
||||
var removedReshapedFields = patch.deletedFields;
|
||||
var renamedReshapedFieldsFrom = patch.renamedFieldsFrom;
|
||||
var renamedReshapedFieldsTo = patch.renamedFieldsTo;
|
||||
|
||||
foreach (var f in removedReshapedFields) {
|
||||
try {
|
||||
var declaringType = SymbolResolver.Resolve(f.declaringType);
|
||||
var fieldType = SymbolResolver.Resolve(f).FieldType;
|
||||
FieldResolver.ClearHolders(declaringType, f.isStatic, f.fieldName, fieldType);
|
||||
} catch (Exception e) {
|
||||
RequestHelper.RequestEditorEventWithRetry(new Stat(StatSource.Client, StatLevel.Error, StatFeature.Patching, StatEventType.ClearHolders), new EditorExtraData {
|
||||
{ StatKey.PatchId, resp.id },
|
||||
{ StatKey.Detailed_Exception, e.ToString() },
|
||||
}).Forget();
|
||||
Log.Warning($"Failed removing field value from {f.fieldName} in {f.declaringType.typeName}. Field value in code might not be up to date. Exception: {e.Message}");
|
||||
}
|
||||
}
|
||||
for (var i = 0; i < renamedReshapedFieldsFrom.Length; i++) {
|
||||
var fromField = renamedReshapedFieldsFrom[i];
|
||||
var toField = renamedReshapedFieldsTo[i];
|
||||
try {
|
||||
var declaringType = SymbolResolver.Resolve(fromField.declaringType);
|
||||
var fieldType = SymbolResolver.Resolve(fromField).FieldType;
|
||||
var toFieldType = SymbolResolver.Resolve(toField).FieldType;
|
||||
if (!AreSTypesCompatible(fromField.declaringType, toField.declaringType)
|
||||
|| fieldType != toFieldType
|
||||
|| fromField.isStatic != toField.isStatic
|
||||
) {
|
||||
FieldResolver.ClearHolders(declaringType, fromField.isStatic, fromField.fieldName, fieldType);
|
||||
continue;
|
||||
}
|
||||
FieldResolver.MoveHolders(declaringType, fromField.fieldName, toField.fieldName, fieldType, fromField.isStatic);
|
||||
} catch (Exception e) {
|
||||
RequestHelper.RequestEditorEventWithRetry(new Stat(StatSource.Client, StatLevel.Error, StatFeature.Patching, StatEventType.MoveHolders), new EditorExtraData {
|
||||
{ StatKey.PatchId, resp.id },
|
||||
{ StatKey.Detailed_Exception, e.ToString() },
|
||||
}).Forget();
|
||||
Log.Warning($"Failed moving field value from {fromField} to {toField} in {toField.declaringType.typeName}. Field value in code might not be up to date. Exception: {e.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal bool AreSTypesCompatible(SType one, SType two) {
|
||||
if (one.isGenericParameter != two.isGenericParameter) {
|
||||
return false;
|
||||
}
|
||||
if (one.metadataToken != two.metadataToken) {
|
||||
return false;
|
||||
}
|
||||
if (one.assemblyName != two.assemblyName) {
|
||||
return false;
|
||||
}
|
||||
if (one.genericParameterPosition != two.genericParameterPosition) {
|
||||
return false;
|
||||
}
|
||||
if (one.typeName != two.typeName) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
internal void RegisterInspectorFieldAttributes(RegisterPatchesResult result, MethodPatchResponse resp) {
|
||||
foreach (var patch in resp.patches) {
|
||||
var propertyAttributesFieldOriginal = patch.propertyAttributesFieldOriginal ?? Array.Empty<SField>();
|
||||
var propertyAttributesFieldUpdated = patch.propertyAttributesFieldUpdated ?? Array.Empty<SField>();
|
||||
for (var i = 0; i < propertyAttributesFieldOriginal.Length; i++) {
|
||||
var original = propertyAttributesFieldOriginal[i];
|
||||
var updated = propertyAttributesFieldUpdated[i];
|
||||
try {
|
||||
var declaringType = SymbolResolver.Resolve(original.declaringType);
|
||||
var originalField = SymbolResolver.Resolve(original);
|
||||
var updatedField = SymbolResolver.Resolve(updated);
|
||||
fieldHandler?.registerInspectorFieldAttributes?.Invoke(declaringType, originalField, updatedField);
|
||||
result.inspectorModified = true;
|
||||
} catch (Exception e) {
|
||||
RequestHelper.RequestEditorEventWithRetry(new Stat(StatSource.Client, StatLevel.Error, StatFeature.Patching, StatEventType.MoveHolders), new EditorExtraData {
|
||||
{ StatKey.PatchId, resp.id },
|
||||
{ StatKey.Detailed_Exception, e.ToString() },
|
||||
}).Forget();
|
||||
Log.Warning($"Failed updating field attributes of {original.fieldName} in {original.declaringType.typeName}. Updates might not reflect in the inspector. Exception: {e.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void HandleNewFields(string patchId, RegisterPatchesResult result, SField[] sFields) {
|
||||
foreach (var sField in sFields) {
|
||||
if (!sField.serializable) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
var declaringType = SymbolResolver.Resolve(sField.declaringType);
|
||||
var field = SymbolResolver.Resolve(sField);
|
||||
fieldHandler?.storeField?.Invoke(declaringType, field);
|
||||
result.inspectorModified = true;
|
||||
} catch (Exception e) {
|
||||
RequestHelper.RequestEditorEventWithRetry(new Stat(StatSource.Client, StatLevel.Error, StatFeature.Patching, StatEventType.AddInspectorField), new EditorExtraData {
|
||||
{ StatKey.PatchId, patchId },
|
||||
{ StatKey.Detailed_Exception, e.ToString() },
|
||||
}).Forget();
|
||||
Log.Warning($"Failed adding field {sField.fieldName}:{sField.declaringType.typeName} to the inspector. Field will not be displayed. Exception: {e.Message}");
|
||||
}
|
||||
}
|
||||
result.addedFields.AddRange(sFields);
|
||||
}
|
||||
|
||||
// IMPORTANT: must come before HandleNewFields. Might contain new fields which we don't want to hide
|
||||
internal void HandleAlteredFields(string patchId, RegisterPatchesResult result, SField[] alteredFields) {
|
||||
if (alteredFields == null) {
|
||||
return;
|
||||
}
|
||||
bool alteredFieldHidden = false;
|
||||
foreach(var sField in alteredFields) {
|
||||
try {
|
||||
var declaringType = SymbolResolver.Resolve(sField.declaringType);
|
||||
if (fieldHandler?.hideField?.Invoke(declaringType, sField.fieldName) == true) {
|
||||
alteredFieldHidden = true;
|
||||
}
|
||||
} catch(Exception e) {
|
||||
RequestHelper.RequestEditorEventWithRetry(new Stat(StatSource.Client, StatLevel.Error, StatFeature.Patching, StatEventType.HideInspectorField), new EditorExtraData {
|
||||
{ StatKey.PatchId, patchId },
|
||||
{ StatKey.Detailed_Exception, e.ToString() },
|
||||
}).Forget();
|
||||
Log.Warning($"Failed hiding field {sField.fieldName}:{sField.declaringType.typeName} from the inspector. Exception: {e.Message}");
|
||||
}
|
||||
}
|
||||
if (alteredFieldHidden) {
|
||||
result.inspectorModified = true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
Dictionary<MethodBase, MethodBase> previousPatchMethods = new Dictionary<MethodBase, MethodBase>();
|
||||
public IEnumerable<MethodBase> OriginalPatchMethods => previousPatchMethods.Keys;
|
||||
List<MethodBase> newMethods = new List<MethodBase>();
|
||||
|
||||
string PatchMethod(Module module, SMethod sOriginalMethod, SMethod sPatchMethod, bool containsBurstJobs, RegisterPatchesResult patchesResult) {
|
||||
string PatchMethod(string patchId, SMethod sOriginalMethod, SMethod sPatchMethod, bool containsBurstJobs, RegisterPatchesResult patchesResult) {
|
||||
try {
|
||||
var patchMethod = module.ResolveMethod(sPatchMethod.metadataToken);
|
||||
var patchMethod = SymbolResolver.Resolve(sPatchMethod);
|
||||
var start = DateTime.UtcNow;
|
||||
var state = TryResolveMethod(sOriginalMethod, patchMethod);
|
||||
if (Debugger.IsAttached && !debuggerCompatibilityEnabled) {
|
||||
RequestHelper.RequestEditorEventWithRetry(new Stat(StatSource.Client, StatLevel.Error, StatFeature.Patching, StatEventType.DebuggerAttached), new EditorExtraData {
|
||||
{ StatKey.PatchId, patchId },
|
||||
}).Forget();
|
||||
return "Patching methods is not allowed while the Debugger is attached. You can change this behavior in settings if Hot Reload is compatible with the debugger you're running.";
|
||||
}
|
||||
|
||||
if (DateTime.UtcNow - start > TimeSpan.FromMilliseconds(500)) {
|
||||
Log.Info("Hot Reload apply took {0}", (DateTime.UtcNow - start).TotalMilliseconds);
|
||||
}
|
||||
|
||||
if(state.match == null) {
|
||||
var error =
|
||||
"Method mismatch: {0}, patch: {1}. This can have multiple reasons:\n"
|
||||
+ "1. You are running the Editor multiple times for the same project using symlinks, and are making changes from the symlink project\n"
|
||||
+ "2. A bug in Hot Reload. Please send us a reproduce (code before/after), and we'll get it fixed for you\n"
|
||||
;
|
||||
Log.Warning(error, sOriginalMethod.simpleName, patchMethod.Name);
|
||||
|
||||
var error = "Edit requires full recompile to apply: Method mismatch: {0}, patch: {1}. \nCommon causes: editing code that failed to patch previously, an unsupported change, or a real bug in Hot Reload.\nIf you think this is a bug, please report the issue on Discord and include a code-snippet before/after.";
|
||||
RequestHelper.RequestEditorEventWithRetry(new Stat(StatSource.Client, StatLevel.Error, StatFeature.Patching, StatEventType.MethodMismatch), new EditorExtraData {
|
||||
{ StatKey.PatchId, patchId },
|
||||
}).Forget();
|
||||
return string.Format(error, sOriginalMethod.simpleName, patchMethod.Name);
|
||||
}
|
||||
|
||||
@@ -239,10 +509,18 @@ namespace SingularityGroup.HotReload {
|
||||
//ignore. The method is likely burst compiled and can't be patched
|
||||
return null;
|
||||
} else {
|
||||
RequestHelper.RequestEditorEventWithRetry(new Stat(StatSource.Client, StatLevel.Error, StatFeature.Patching, StatEventType.Failure), new EditorExtraData {
|
||||
{ StatKey.PatchId, patchId },
|
||||
{ StatKey.Detailed_Exception, result.exception.ToString() },
|
||||
}).Forget();
|
||||
return HandleMethodPatchFailure(sOriginalMethod, result.exception);
|
||||
}
|
||||
}
|
||||
} catch(Exception ex) {
|
||||
RequestHelper.RequestEditorEventWithRetry(new Stat(StatSource.Client, StatLevel.Error, StatFeature.Patching, StatEventType.Exception), new EditorExtraData {
|
||||
{ StatKey.PatchId, patchId },
|
||||
{ StatKey.Detailed_Exception, ex.ToString() },
|
||||
}).Forget();
|
||||
return HandleMethodPatchFailure(sOriginalMethod, ex);
|
||||
}
|
||||
}
|
||||
@@ -316,7 +594,11 @@ namespace SingularityGroup.HotReload {
|
||||
MethodBase resolvedMethod = null;
|
||||
try {
|
||||
resolvedMethod = TryGetMethodBaseWithRelativeToken(methodToResolve, offset);
|
||||
if(!MethodCompatiblity.AreMethodsCompatible(resolvedMethod, patchMethod)) {
|
||||
var err = MethodCompatiblity.CheckCompatibility(resolvedMethod, patchMethod);
|
||||
if(err != null) {
|
||||
// if (resolvedMethod.Name == patchMethod.Name) {
|
||||
// Log.Info(err);
|
||||
// }
|
||||
resolvedMethod = null;
|
||||
}
|
||||
} catch (SymbolResolvingFailedException ex) when(ex.InnerException is ArgumentOutOfRangeException) {
|
||||
@@ -331,15 +613,11 @@ namespace SingularityGroup.HotReload {
|
||||
return symbolResolver.Resolve(new SMethod(sOriginalMethod.assemblyName,
|
||||
sOriginalMethod.displayName,
|
||||
sOriginalMethod.metadataToken + offset,
|
||||
sOriginalMethod.genericTypeArguments,
|
||||
sOriginalMethod.genericTypeArguments,
|
||||
sOriginalMethod.simpleName));
|
||||
}
|
||||
|
||||
string HandleMethodPatchFailure(SMethod method, Exception exception) {
|
||||
var err = $"Failed to apply patch for method {method.displayName} in assembly {method.assemblyName}\n{exception}";
|
||||
Log.Warning(err);
|
||||
return err;
|
||||
return $"Edit requires full recompile to apply: Failed to apply patch for method {method.displayName} in assembly {method.assemblyName}.\nCommon causes: editing code that failed to patch previously, an unsupported change, or a real bug in Hot Reload.\nIf you think this is a bug, please report the issue on Discord and include a code-snippet before/after.\nException: {exception}";
|
||||
}
|
||||
|
||||
void EnsureSymbolResolver() {
|
||||
|
||||
@@ -38,12 +38,12 @@ RenderSettings:
|
||||
m_ReflectionIntensity: 1
|
||||
m_CustomReflection: {fileID: 0}
|
||||
m_Sun: {fileID: 0}
|
||||
m_IndirectSpecularColor: {r: 0.18028378, g: 0.22571412, b: 0.30692285, a: 1}
|
||||
m_IndirectSpecularColor: {r: 0.44657898, g: 0.4964133, b: 0.5748178, a: 1}
|
||||
m_UseRadianceAmbientProbe: 0
|
||||
--- !u!157 &3
|
||||
LightmapSettings:
|
||||
m_ObjectHideFlags: 0
|
||||
serializedVersion: 12
|
||||
serializedVersion: 11
|
||||
m_GIWorkflowMode: 0
|
||||
m_GISettings:
|
||||
serializedVersion: 2
|
||||
@@ -98,13 +98,13 @@ LightmapSettings:
|
||||
m_TrainingDataDestination: TrainingData
|
||||
m_LightProbeSampleCountMultiplier: 4
|
||||
m_LightingDataAsset: {fileID: 0}
|
||||
m_LightingSettings: {fileID: 4890085278179872738, guid: 463b4a464af955e4d8d6b0a2923d94d0, type: 2}
|
||||
m_UseShadowmask: 1
|
||||
--- !u!196 &4
|
||||
NavMeshSettings:
|
||||
serializedVersion: 2
|
||||
m_ObjectHideFlags: 0
|
||||
m_BuildSettings:
|
||||
serializedVersion: 3
|
||||
serializedVersion: 2
|
||||
agentTypeID: 0
|
||||
agentRadius: 0.5
|
||||
agentHeight: 2
|
||||
@@ -117,9 +117,7 @@ NavMeshSettings:
|
||||
cellSize: 0.16666667
|
||||
manualTileSize: 0
|
||||
tileSize: 256
|
||||
buildHeightMesh: 0
|
||||
maxJobWorkers: 0
|
||||
preserveTilesOutsideBounds: 0
|
||||
accuratePlacement: 0
|
||||
debug:
|
||||
m_Flags: 0
|
||||
m_NavMeshData: {fileID: 0}
|
||||
@@ -150,18 +148,10 @@ BoxCollider:
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 19295889}
|
||||
m_Material: {fileID: 0}
|
||||
m_IncludeLayers:
|
||||
serializedVersion: 2
|
||||
m_Bits: 0
|
||||
m_ExcludeLayers:
|
||||
serializedVersion: 2
|
||||
m_Bits: 0
|
||||
m_LayerOverridePriority: 0
|
||||
m_IsTrigger: 0
|
||||
m_ProvidesContacts: 0
|
||||
m_Enabled: 1
|
||||
serializedVersion: 3
|
||||
m_Size: {x: 1, y: 1, z: 1}
|
||||
serializedVersion: 2
|
||||
m_Size: {x: 2, y: 2, z: 2}
|
||||
m_Center: {x: 0, y: 0, z: 0}
|
||||
--- !u!23 &19295891
|
||||
MeshRenderer:
|
||||
@@ -174,12 +164,10 @@ MeshRenderer:
|
||||
m_CastShadows: 1
|
||||
m_ReceiveShadows: 1
|
||||
m_DynamicOccludee: 1
|
||||
m_StaticShadowCaster: 0
|
||||
m_MotionVectors: 1
|
||||
m_LightProbeUsage: 1
|
||||
m_ReflectionProbeUsage: 1
|
||||
m_RayTracingMode: 2
|
||||
m_RayTraceProcedural: 0
|
||||
m_RenderingLayerMask: 1
|
||||
m_RendererPriority: 0
|
||||
m_Materials:
|
||||
@@ -204,7 +192,6 @@ MeshRenderer:
|
||||
m_SortingLayerID: 0
|
||||
m_SortingLayer: 0
|
||||
m_SortingOrder: 0
|
||||
m_AdditionalVertexStreams: {fileID: 0}
|
||||
--- !u!33 &19295892
|
||||
MeshFilter:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -223,7 +210,6 @@ Transform:
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_RootOrder: 4
|
||||
@@ -268,17 +254,9 @@ Camera:
|
||||
m_projectionMatrixMode: 1
|
||||
m_GateFitMode: 2
|
||||
m_FOVAxisMode: 0
|
||||
m_Iso: 200
|
||||
m_ShutterSpeed: 0.005
|
||||
m_Aperture: 16
|
||||
m_FocusDistance: 10
|
||||
m_FocalLength: 50
|
||||
m_BladeCount: 5
|
||||
m_Curvature: {x: 2, y: 11}
|
||||
m_BarrelClipping: 0.25
|
||||
m_Anamorphism: 0
|
||||
m_SensorSize: {x: 36, y: 24}
|
||||
m_LensShift: {x: 0, y: 0}
|
||||
m_FocalLength: 50
|
||||
m_NormalizedViewPortRect:
|
||||
serializedVersion: 2
|
||||
x: 0
|
||||
@@ -315,7 +293,6 @@ Transform:
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 1, z: -10}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_RootOrder: 0
|
||||
@@ -348,7 +325,6 @@ RectTransform:
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 511172213}
|
||||
m_RootOrder: 0
|
||||
@@ -373,7 +349,6 @@ MonoBehaviour:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1}
|
||||
m_RaycastTarget: 1
|
||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_Maskable: 1
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
@@ -429,7 +404,6 @@ RectTransform:
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children:
|
||||
- {fileID: 460271677}
|
||||
m_Father: {fileID: 649153321}
|
||||
@@ -454,7 +428,6 @@ MonoBehaviour:
|
||||
m_EditorClassIdentifier:
|
||||
m_Navigation:
|
||||
m_Mode: 3
|
||||
m_WrapAround: 0
|
||||
m_SelectOnUp: {fileID: 0}
|
||||
m_SelectOnDown: {fileID: 0}
|
||||
m_SelectOnLeft: {fileID: 0}
|
||||
@@ -499,7 +472,6 @@ MonoBehaviour:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_RaycastTarget: 1
|
||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_Maskable: 1
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
@@ -580,7 +552,6 @@ MonoBehaviour:
|
||||
m_FallbackScreenDPI: 96
|
||||
m_DefaultSpriteDPI: 96
|
||||
m_DynamicPixelsPerUnit: 1
|
||||
m_PresetInfoIsWorld: 0
|
||||
--- !u!223 &649153320
|
||||
Canvas:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -599,7 +570,6 @@ Canvas:
|
||||
m_OverridePixelPerfect: 0
|
||||
m_SortingBucketNormalizedSize: 0
|
||||
m_AdditionalShaderChannelsFlag: 0
|
||||
m_UpdateRectTransformForStandalone: 0
|
||||
m_SortingLayerID: 0
|
||||
m_SortingOrder: 0
|
||||
m_TargetDisplay: 0
|
||||
@@ -613,7 +583,6 @@ RectTransform:
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 0, y: 0, z: 0}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children:
|
||||
- {fileID: 1537524790}
|
||||
- {fileID: 1847025553}
|
||||
@@ -656,7 +625,6 @@ MonoBehaviour:
|
||||
m_Script: {fileID: 11500000, guid: 4f231c4fb786f3946a6b90b886c48677, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_SendPointerHoverToParent: 1
|
||||
m_HorizontalAxis: Horizontal
|
||||
m_VerticalAxis: Vertical
|
||||
m_SubmitButton: Submit
|
||||
@@ -689,7 +657,6 @@ Transform:
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_RootOrder: 5
|
||||
@@ -770,7 +737,6 @@ Light:
|
||||
m_UseColorTemperature: 0
|
||||
m_BoundingSphereOverride: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_UseBoundingSphereOverride: 0
|
||||
m_UseViewFrustumForShadowCasterCull: 1
|
||||
m_ShadowRadius: 0
|
||||
m_ShadowAngle: 0
|
||||
--- !u!4 &965437872
|
||||
@@ -783,7 +749,6 @@ Transform:
|
||||
m_LocalRotation: {x: 0.40821788, y: -0.23456968, z: 0.10938163, w: 0.8754261}
|
||||
m_LocalPosition: {x: 0, y: 3, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_RootOrder: 1
|
||||
@@ -816,7 +781,6 @@ RectTransform:
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 1847025553}
|
||||
m_RootOrder: 0
|
||||
@@ -841,7 +805,6 @@ MonoBehaviour:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1}
|
||||
m_RaycastTarget: 1
|
||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_Maskable: 1
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
@@ -896,7 +859,6 @@ RectTransform:
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 649153321}
|
||||
m_RootOrder: 0
|
||||
@@ -921,7 +883,6 @@ MonoBehaviour:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 0, g: 0, b: 0, a: 1}
|
||||
m_RaycastTarget: 1
|
||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_Maskable: 1
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
@@ -977,7 +938,6 @@ RectTransform:
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children:
|
||||
- {fileID: 1101930859}
|
||||
m_Father: {fileID: 649153321}
|
||||
@@ -1002,7 +962,6 @@ MonoBehaviour:
|
||||
m_EditorClassIdentifier:
|
||||
m_Navigation:
|
||||
m_Mode: 3
|
||||
m_WrapAround: 0
|
||||
m_SelectOnUp: {fileID: 0}
|
||||
m_SelectOnDown: {fileID: 0}
|
||||
m_SelectOnLeft: {fileID: 0}
|
||||
@@ -1047,7 +1006,6 @@ MonoBehaviour:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_RaycastTarget: 1
|
||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_Maskable: 1
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
@@ -1097,7 +1055,6 @@ Transform:
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 18.716805, y: 53.419094, z: 92.546875}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_RootOrder: 2
|
||||
@@ -1119,3 +1076,4 @@ MonoBehaviour:
|
||||
openWindowButton: {fileID: 1847025554}
|
||||
openScriptButton: {fileID: 511172214}
|
||||
thisScript: {fileID: 11500000, guid: 5a2e4d3f095a9441688c70278068eee0, type: 3}
|
||||
myEditedField: 54
|
||||
|
||||
@@ -8,24 +8,27 @@ using UnityEngine.UI;
|
||||
|
||||
namespace SingularityGroup.HotReload.Demo {
|
||||
class HotReloadBasicDemo : MonoBehaviour {
|
||||
|
||||
|
||||
public GameObject cube;
|
||||
public Text informationText;
|
||||
public Button openWindowButton;
|
||||
public Button openScriptButton;
|
||||
public TextAsset thisScript;
|
||||
|
||||
|
||||
// // 1. Adding fields (Added fields can show in the inspector)
|
||||
// public int myNewField = 1;
|
||||
|
||||
void Start() {
|
||||
if(Application.isEditor) {
|
||||
if (Application.isEditor) {
|
||||
openWindowButton.onClick.AddListener(Demo.I.OpenHotReloadWindow);
|
||||
openScriptButton.onClick.AddListener(() => Demo.I.OpenScriptFile(thisScript, 31, 13));
|
||||
openScriptButton.onClick.AddListener(() => Demo.I.OpenScriptFile(thisScript, myStaticField, 13));
|
||||
} else {
|
||||
openWindowButton.gameObject.SetActive(false);
|
||||
openScriptButton.gameObject.SetActive(false);
|
||||
informationText.gameObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Update is called once per frame
|
||||
void Update() {
|
||||
if (Demo.I.IsServerRunning()) {
|
||||
@@ -33,25 +36,25 @@ namespace SingularityGroup.HotReload.Demo {
|
||||
} else {
|
||||
informationText.text = "Hot Reload is not running";
|
||||
}
|
||||
|
||||
// // 1. Editing functions in monobehaviours, normal classes or static classes
|
||||
|
||||
// // 2. Editing functions in monobehaviours, normal classes or static classes
|
||||
// // Edit the vector to rotate the cube in the scene differently or change the speed
|
||||
// var speed = 100;
|
||||
// cube.transform.Rotate(new Vector3(0, 1, 0) * Time.deltaTime * speed);
|
||||
|
||||
// // 1. Editing functions in monobehaviours, normal classes or static classes
|
||||
|
||||
// // 2. Editing functions in monobehaviours, normal classes or static classes
|
||||
// // Uncomment this code to scale the cube
|
||||
// cube.transform.localScale = Mathf.Sin(Time.time) * Vector3.one;
|
||||
|
||||
// // 1. Editing functions in monobehaviours, normal classes or static classes
|
||||
|
||||
// // 2. Editing functions in monobehaviours, normal classes or static classes
|
||||
// // Uncomment this code to make the cube move from left to right and back
|
||||
// var newPos = cube.transform.position += (cube.transform.localScale.x < 0.5 ? Vector3.left : Vector3.right) * Time.deltaTime;
|
||||
// if(Mathf.Abs(newPos.x) > 10) {
|
||||
// cube.transform.position = Vector3.zero;
|
||||
// }
|
||||
}
|
||||
|
||||
// 2. Editing lambda methods
|
||||
|
||||
// 3. Editing lambda methods
|
||||
static Func<int, int> addFunction = x => {
|
||||
var result = x + 10;
|
||||
Debug.Log("Add: " + result);
|
||||
@@ -60,17 +63,17 @@ namespace SingularityGroup.HotReload.Demo {
|
||||
// Debug.Log("Multiply: " + result);
|
||||
return result;
|
||||
};
|
||||
|
||||
// 3. Editing async/await methods
|
||||
|
||||
// 4. Editing async/await methods
|
||||
async Task AsyncMethod() {
|
||||
// await Task.Delay(500);
|
||||
// Debug.Log("AsyncMethod");
|
||||
|
||||
|
||||
// // silicense warning
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
// 4. Editing properties (get/set)
|
||||
|
||||
// 5. Editing properties (get/set)
|
||||
public static string SomeString {
|
||||
// edit the get method
|
||||
get {
|
||||
@@ -78,8 +81,8 @@ namespace SingularityGroup.HotReload.Demo {
|
||||
return someStringHere;
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Editing indexers (square bracket access such as dictionaries)
|
||||
|
||||
// 6. Editing indexers (square bracket access such as dictionaries)
|
||||
class CustomDictionary : Dictionary<string, int> {
|
||||
public new int this[string key] {
|
||||
get {
|
||||
@@ -102,8 +105,8 @@ namespace SingularityGroup.HotReload.Demo {
|
||||
{ "d", 19 },
|
||||
{ "D", 20 }
|
||||
};
|
||||
|
||||
// 6. Editing operators methods (explicit and implicit operators)
|
||||
|
||||
// 7. Editing operators methods (explicit and implicit operators)
|
||||
public class Email {
|
||||
public string Value { get; }
|
||||
|
||||
@@ -116,7 +119,7 @@ namespace SingularityGroup.HotReload.Demo {
|
||||
// Uncomment to change the implicit operator
|
||||
// => value.Value + " FOO";
|
||||
=> value.Value;
|
||||
|
||||
|
||||
// // Uncomment to change add an implicit operator
|
||||
// public static implicit operator byte[](Email value)
|
||||
// => Encoding.UTF8.GetBytes(value.Value);
|
||||
@@ -126,51 +129,86 @@ namespace SingularityGroup.HotReload.Demo {
|
||||
=> new Email(value);
|
||||
}
|
||||
|
||||
// 8. Editing fields: modifiers/type/name/initializer
|
||||
public int myEditedField = 4;
|
||||
|
||||
// 9. Editing static field initializers (variable value is updated)
|
||||
static readonly int myStaticField = 31;
|
||||
|
||||
// // 10. Adding auto properties/events
|
||||
// int MyProperty { get; set; } = 6;
|
||||
// event Action MyEvent = () => Debug.Log("MyEvent");
|
||||
|
||||
class GenericClass<T> {
|
||||
// // 11. Adding methods in generic classes
|
||||
// public void GenericMethod() {
|
||||
// Debug.Log("GenericMethod");
|
||||
// }
|
||||
// // 12. Adding fields (any type) in generic classes
|
||||
// public T myGenericField;
|
||||
}
|
||||
|
||||
void LateUpdate() {
|
||||
// // 2. Editing lambda methods
|
||||
// // 3. Editing lambda methods
|
||||
// addFunction(10);
|
||||
|
||||
|
||||
// // 3. Editing async/await methods
|
||||
|
||||
|
||||
// // 4. Editing async/await methods
|
||||
// AsyncMethod().Forget();
|
||||
|
||||
|
||||
// // 4. Editing properties (get/set)
|
||||
|
||||
|
||||
// // 5. Editing properties (get/set)
|
||||
// Debug.Log(SomeString);
|
||||
|
||||
|
||||
// // 5. Editing indexers (square bracket access such as dictionaries)
|
||||
|
||||
|
||||
// // 6. Editing indexers (square bracket access such as dictionaries)
|
||||
// Debug.Log(randomDict["A"]);
|
||||
|
||||
|
||||
// // 6. Editing operators methods (explicit and implicit operators)
|
||||
|
||||
|
||||
// // 7. Editing operators methods (explicit and implicit operators)
|
||||
Email email = new Email("example@example.com");
|
||||
// string stringEmail = email;
|
||||
// Debug.Log(stringEmail);
|
||||
|
||||
|
||||
// // Uncomment new operator in Email class + Uncomment this to add byte implicit operator
|
||||
// byte[] byteEmail = email;
|
||||
// var hexRepresentation = BitConverter.ToString(byteEmail);
|
||||
// Debug.Log(hexRepresentation);
|
||||
// Debug.Log(Encoding.UTF8.GetString(byteEmail));
|
||||
|
||||
// // 8. Editing fields: modifiers/type/name/initializer
|
||||
// Debug.Log("myEditedField: " + myEditedField);
|
||||
|
||||
// // 9. Editing static field initializers (variable value is updated)
|
||||
// Debug.Log("myStaticField: " + myStaticField);
|
||||
|
||||
// // 10. Adding auto properties/events
|
||||
// Debug.Log("MyProperty: " + MyProperty);
|
||||
// MyEvent.Invoke();
|
||||
|
||||
// // 7. Editing lambda methods with closures
|
||||
// var newClass = new GenericClass<int>();
|
||||
// // 11. Adding methods in generic classes
|
||||
// newClass.GenericMethod();
|
||||
// // 12. Adding fields in generic classes
|
||||
// newClass.myGenericField = 3;
|
||||
// Debug.Log("myGenericField: " + newClass.myGenericField);
|
||||
|
||||
// // 13. Editing lambda methods with closures
|
||||
// // Uncomment to log sorted array
|
||||
// // Switch a and b to reverse the sorting
|
||||
// int[] numbers = { 5, 3, 8, 1, 9 };
|
||||
// Array.Sort(numbers, (b, a) => a.CompareTo(b));
|
||||
// Debug.Log(string.Join(", ", numbers));
|
||||
|
||||
}
|
||||
|
||||
|
||||
// This function gets invoked every time it's patched
|
||||
[InvokeOnHotReloadLocal]
|
||||
static void OnHotReloadMe() {
|
||||
// change the string to see the method getting invoked
|
||||
Debug.Log("Hello there");
|
||||
// // change the string to see the method getting invoked
|
||||
// Debug.Log("Hello there");
|
||||
}
|
||||
|
||||
// // 8. Adding event functions
|
||||
// // 14. Adding event functions
|
||||
// void OnDisable() {
|
||||
// Debug.Log("OnDisable");
|
||||
// }
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -6,7 +6,7 @@ using SingularityGroup.HotReload.MonoMod.Utils;
|
||||
|
||||
namespace SingularityGroup.HotReload {
|
||||
static class MethodCompatiblity {
|
||||
internal static bool AreMethodsCompatible(MethodBase previousMethod, MethodBase patchMethod) {
|
||||
internal static string CheckCompatibility(MethodBase previousMethod, MethodBase patchMethod) {
|
||||
var previousConstructor = previousMethod as ConstructorInfo;
|
||||
var patchConstructor = patchMethod as ConstructorInfo;
|
||||
if(previousConstructor != null && !ReferenceEquals(patchConstructor, null)) {
|
||||
@@ -17,20 +17,29 @@ namespace SingularityGroup.HotReload {
|
||||
if(!ReferenceEquals(previousMethodInfo, null) && !ReferenceEquals(patchMethodInfo, null)) {
|
||||
return AreMethodInfosCompatible(previousMethodInfo, patchMethodInfo);
|
||||
}
|
||||
return false;
|
||||
return "unknown issue";
|
||||
}
|
||||
|
||||
static bool AreMethodBasesCompatible(MethodBase previousMethod, MethodBase patchMethod) {
|
||||
static string AreMethodBasesCompatible(MethodBase previousMethod, MethodBase patchMethod) {
|
||||
if(previousMethod.Name != patchMethod.Name) {
|
||||
return false;
|
||||
return "Method name mismatch";
|
||||
}
|
||||
//Declaring type of patch method is different from the target method but their full name (namespace + name) is equal
|
||||
if(previousMethod.DeclaringType.FullName != patchMethod.DeclaringType.FullName) {
|
||||
return false;
|
||||
bool isDeclaringTypeCompatible = false;
|
||||
var declaringType = patchMethod.DeclaringType;
|
||||
while (declaringType != null) {
|
||||
if(previousMethod.DeclaringType?.FullName == declaringType.FullName) {
|
||||
isDeclaringTypeCompatible = true;
|
||||
break;
|
||||
}
|
||||
declaringType = declaringType.BaseType;
|
||||
}
|
||||
if (!isDeclaringTypeCompatible) {
|
||||
return "Declaring type name mismatch";
|
||||
}
|
||||
//Check in case type parameter overloads to distinguish between: void M<T>() { } <-> void M() { }
|
||||
if(previousMethod.IsGenericMethodDefinition != patchMethod.IsGenericMethodDefinition) {
|
||||
return false;
|
||||
return "IsGenericMethodDefinition mismatch";
|
||||
}
|
||||
|
||||
var prevParams = previousMethod.GetParameters();
|
||||
@@ -53,11 +62,11 @@ namespace SingularityGroup.HotReload {
|
||||
//Special case: patch method for an instance method is static and has an explicit this parameter.
|
||||
//If the patch method doesn't have any parameters it is not compatible.
|
||||
if(patchParams.Length == 0) {
|
||||
return false;
|
||||
return "missing this parameter";
|
||||
}
|
||||
//this parameter has to be the declaring type
|
||||
if(!ParamTypeMatches(patchParams[0].ParameterType, previousMethod.DeclaringType)) {
|
||||
return false;
|
||||
return "this parameter type mismatch";
|
||||
}
|
||||
//Ignore the this parameter and compare the remaining ones.
|
||||
patchParamsSegment = new ArraySegment<ParameterInfo>(patchParams, 1, patchParams.Length - 1);
|
||||
@@ -75,9 +84,6 @@ namespace SingularityGroup.HotReload {
|
||||
if (!ParamTypeMatches(patchT, previousMethod.DeclaringType)) {
|
||||
return false;
|
||||
}
|
||||
if (prevParams.Length >= 1 && prevParams[0].ParameterType == previousMethod.DeclaringType) {
|
||||
return false;
|
||||
}
|
||||
return patchParams[0].Name == "this";
|
||||
}
|
||||
|
||||
@@ -85,25 +91,25 @@ namespace SingularityGroup.HotReload {
|
||||
return patchT == originalT || patchT.IsByRef && patchT.GetElementType() == originalT;
|
||||
}
|
||||
|
||||
static bool CompareParameters(ArraySegment<ParameterInfo> x, ArraySegment<ParameterInfo> y) {
|
||||
static string CompareParameters(ArraySegment<ParameterInfo> x, ArraySegment<ParameterInfo> y) {
|
||||
if(x.Count != y.Count) {
|
||||
return false;
|
||||
return "parameter count mismatch";
|
||||
}
|
||||
for (var i = 0; i < x.Count; i++) {
|
||||
if(x.Array[i + x.Offset].ParameterType != y.Array[i + y.Offset].ParameterType) {
|
||||
return false;
|
||||
return "parameter type mismatch";
|
||||
}
|
||||
}
|
||||
return true;
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
static bool AreConstructorsCompatible(ConstructorInfo x, ConstructorInfo y) {
|
||||
static string AreConstructorsCompatible(ConstructorInfo x, ConstructorInfo y) {
|
||||
return AreMethodBasesCompatible(x, y);
|
||||
}
|
||||
|
||||
static bool AreMethodInfosCompatible(MethodInfo x, MethodInfo y) {
|
||||
return AreMethodBasesCompatible(x, y) && x.ReturnType == y.ReturnType;
|
||||
static string AreMethodInfosCompatible(MethodInfo x, MethodInfo y) {
|
||||
return AreMethodBasesCompatible(x, y) ?? (x.ReturnType == y.ReturnType ? null : "Return type mismatch");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,11 @@ namespace SingularityGroup.HotReload.JsonConverters {
|
||||
CodePatch[] patches = null;
|
||||
string[] failures = null;
|
||||
SMethod[] removedMethod = null;
|
||||
SField[] alteredFields = null;
|
||||
SField[] addedFieldInitializerFields = null;
|
||||
SMethod[] addedFieldInitializerInitializers = null;
|
||||
SField[] removedFieldInitializers = null;
|
||||
SField[] newFieldDefinitions = null;
|
||||
|
||||
while (reader.Read()) {
|
||||
if (reader.TokenType == JsonToken.EndObject) {
|
||||
@@ -56,6 +61,26 @@ namespace SingularityGroup.HotReload.JsonConverters {
|
||||
case nameof(MethodPatchResponse.removedMethod):
|
||||
removedMethod = ReadSMethodArray(reader);
|
||||
break;
|
||||
|
||||
case nameof(MethodPatchResponse.alteredFields):
|
||||
alteredFields = ReadSFields(reader);
|
||||
break;
|
||||
|
||||
case nameof(MethodPatchResponse.addedFieldInitializerFields):
|
||||
addedFieldInitializerFields = ReadSFields(reader);
|
||||
break;
|
||||
|
||||
case nameof(MethodPatchResponse.addedFieldInitializerInitializers):
|
||||
addedFieldInitializerInitializers = ReadSMethodArray(reader);
|
||||
break;
|
||||
|
||||
case nameof(MethodPatchResponse.removedFieldInitializers):
|
||||
removedFieldInitializers = ReadSFields(reader);
|
||||
break;
|
||||
|
||||
case nameof(MethodPatchResponse.newFieldDefinitions):
|
||||
newFieldDefinitions = ReadSFields(reader);
|
||||
break;
|
||||
|
||||
default:
|
||||
reader.Skip(); // Skip unknown properties
|
||||
@@ -68,8 +93,14 @@ namespace SingularityGroup.HotReload.JsonConverters {
|
||||
patches ?? Array.Empty<CodePatch>(),
|
||||
failures ?? Array.Empty<string>(),
|
||||
removedMethod ?? Array.Empty<SMethod>(),
|
||||
// Note: doesn't have to be persisted here
|
||||
Array.Empty<PartiallySupportedChange>()
|
||||
alteredFields ?? Array.Empty<SField>(),
|
||||
// Note: suggestions don't have to be persisted here
|
||||
Array.Empty<PartiallySupportedChange>(),
|
||||
Array.Empty<HotReloadSuggestionKind>(),
|
||||
addedFieldInitializerFields ?? Array.Empty<SField>(),
|
||||
addedFieldInitializerInitializers ?? Array.Empty<SMethod>(),
|
||||
removedFieldInitializers ?? Array.Empty<SField>(),
|
||||
newFieldDefinitions ?? Array.Empty<SField>()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -91,6 +122,12 @@ namespace SingularityGroup.HotReload.JsonConverters {
|
||||
SMethod[] patchMethods = null;
|
||||
SMethod[] newMethods = null;
|
||||
SUnityJob[] unityJobs = null;
|
||||
SField[] newFields = null;
|
||||
SField[] deletedFields = null;
|
||||
SField[] renamedFieldsFrom = null;
|
||||
SField[] renamedFieldsTo = null;
|
||||
SField[] propertyAttributesFieldOriginal = null;
|
||||
SField[] propertyAttributesFieldUpdated = null;
|
||||
|
||||
while (reader.Read()) {
|
||||
if (reader.TokenType == JsonToken.EndObject) {
|
||||
@@ -133,6 +170,29 @@ namespace SingularityGroup.HotReload.JsonConverters {
|
||||
case nameof(CodePatch.unityJobs):
|
||||
unityJobs = ReadSUnityJobArray(reader);
|
||||
break;
|
||||
|
||||
case nameof(CodePatch.newFields):
|
||||
newFields = ReadSFields(reader);
|
||||
break;
|
||||
case nameof(CodePatch.deletedFields):
|
||||
deletedFields = ReadSFields(reader);
|
||||
break;
|
||||
|
||||
case nameof(CodePatch.renamedFieldsFrom):
|
||||
renamedFieldsFrom = ReadSFields(reader);
|
||||
break;
|
||||
|
||||
case nameof(CodePatch.renamedFieldsTo):
|
||||
renamedFieldsTo = ReadSFields(reader);
|
||||
break;
|
||||
|
||||
case nameof(CodePatch.propertyAttributesFieldOriginal):
|
||||
propertyAttributesFieldOriginal = ReadSFields(reader);
|
||||
break;
|
||||
|
||||
case nameof(CodePatch.propertyAttributesFieldUpdated):
|
||||
propertyAttributesFieldUpdated = ReadSFields(reader);
|
||||
break;
|
||||
|
||||
default:
|
||||
reader.Skip(); // Skip unknown properties
|
||||
@@ -148,7 +208,13 @@ namespace SingularityGroup.HotReload.JsonConverters {
|
||||
modifiedMethods: modifiedMethods ?? Array.Empty<SMethod>(),
|
||||
patchMethods: patchMethods ?? Array.Empty<SMethod>(),
|
||||
newMethods: newMethods ?? Array.Empty<SMethod>(),
|
||||
unityJobs: unityJobs ?? Array.Empty<SUnityJob>()
|
||||
unityJobs: unityJobs ?? Array.Empty<SUnityJob>(),
|
||||
newFields: newFields ?? Array.Empty<SField>(),
|
||||
deletedFields: deletedFields ?? Array.Empty<SField>(),
|
||||
renamedFieldsFrom: renamedFieldsFrom ?? Array.Empty<SField>(),
|
||||
renamedFieldsTo: renamedFieldsTo ?? Array.Empty<SField>(),
|
||||
propertyAttributesFieldOriginal: propertyAttributesFieldOriginal ?? Array.Empty<SField>(),
|
||||
propertyAttributesFieldUpdated: propertyAttributesFieldUpdated ?? Array.Empty<SField>()
|
||||
));
|
||||
}
|
||||
|
||||
@@ -210,13 +276,23 @@ namespace SingularityGroup.HotReload.JsonConverters {
|
||||
|
||||
return array.ToArray();
|
||||
}
|
||||
|
||||
private SField[] ReadSFields(JsonReader reader) {
|
||||
var array = new List<SField>();
|
||||
while (reader.Read()) {
|
||||
if (reader.TokenType == JsonToken.StartObject) {
|
||||
array.Add(ReadSField(reader));
|
||||
} else if (reader.TokenType == JsonToken.EndArray) {
|
||||
break; // End of the SUnityJob array
|
||||
}
|
||||
}
|
||||
return array.ToArray();
|
||||
}
|
||||
|
||||
private SMethod ReadSMethod(JsonReader reader) {
|
||||
string assemblyName = null;
|
||||
string displayName = null;
|
||||
int metadataToken = default(int);
|
||||
SType[] genericTypeArguments = null;
|
||||
SType[] genericArguments = null;
|
||||
string simpleName = null;
|
||||
|
||||
while (reader.Read()) {
|
||||
@@ -241,14 +317,6 @@ namespace SingularityGroup.HotReload.JsonConverters {
|
||||
metadataToken = reader.ReadAsInt32() ?? default(int);
|
||||
break;
|
||||
|
||||
case nameof(SMethod.genericTypeArguments):
|
||||
genericTypeArguments = ReadSTypeArray(reader);
|
||||
break;
|
||||
|
||||
case nameof(SMethod.genericArguments):
|
||||
genericArguments = ReadSTypeArray(reader);
|
||||
break;
|
||||
|
||||
case nameof(SMethod.simpleName):
|
||||
simpleName = reader.ReadAsString();
|
||||
break;
|
||||
@@ -263,8 +331,6 @@ namespace SingularityGroup.HotReload.JsonConverters {
|
||||
assemblyName ?? string.Empty,
|
||||
displayName ?? string.Empty,
|
||||
metadataToken,
|
||||
genericTypeArguments ?? Array.Empty<SType>(),
|
||||
genericArguments ?? Array.Empty<SType>(),
|
||||
simpleName ?? string.Empty
|
||||
);
|
||||
}
|
||||
@@ -272,9 +338,12 @@ namespace SingularityGroup.HotReload.JsonConverters {
|
||||
private SType ReadSType(JsonReader reader) {
|
||||
string assemblyName = null;
|
||||
string typeName = null;
|
||||
SType[] genericArguments = null;
|
||||
int? metadataToken = null;
|
||||
|
||||
while (reader.Read()) {
|
||||
if (reader.TokenType == JsonToken.Null) {
|
||||
return null;
|
||||
}
|
||||
if (reader.TokenType == JsonToken.EndObject) {
|
||||
break;
|
||||
}
|
||||
@@ -292,8 +361,8 @@ namespace SingularityGroup.HotReload.JsonConverters {
|
||||
typeName = reader.ReadAsString();
|
||||
break;
|
||||
|
||||
case nameof(SType.genericArguments):
|
||||
genericArguments = ReadSTypeArray(reader);
|
||||
case nameof(SType.metadataToken):
|
||||
metadataToken = reader.ReadAsInt32();
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -305,7 +374,7 @@ namespace SingularityGroup.HotReload.JsonConverters {
|
||||
return new SType(
|
||||
assemblyName ?? string.Empty,
|
||||
typeName ?? string.Empty,
|
||||
genericArguments ?? Array.Empty<SType>()
|
||||
metadataToken ?? 0
|
||||
);
|
||||
}
|
||||
|
||||
@@ -340,6 +409,57 @@ namespace SingularityGroup.HotReload.JsonConverters {
|
||||
|
||||
return new SUnityJob(metadataToken, jobKind);
|
||||
}
|
||||
|
||||
private SField ReadSField(JsonReader reader) {
|
||||
SType declaringType = null;
|
||||
string fieldName = null;
|
||||
string assemblyName = null;
|
||||
int? metadataToken = null;
|
||||
bool? serializable = null;
|
||||
bool? isStatic = null;
|
||||
|
||||
while (reader.Read()) {
|
||||
if (reader.TokenType == JsonToken.EndObject) {
|
||||
break;
|
||||
}
|
||||
if (reader.TokenType != JsonToken.PropertyName) {
|
||||
continue;
|
||||
}
|
||||
var propertyName = (string)reader.Value;
|
||||
|
||||
switch (propertyName) {
|
||||
case nameof(SField.declaringType):
|
||||
declaringType = ReadSType(reader);
|
||||
break;
|
||||
|
||||
case nameof(SField.fieldName):
|
||||
fieldName = reader.ReadAsString();
|
||||
break;
|
||||
|
||||
case nameof(SField.assemblyName):
|
||||
assemblyName = reader.ReadAsString();
|
||||
break;
|
||||
|
||||
case nameof(SField.metadataToken):
|
||||
metadataToken = reader.ReadAsInt32();
|
||||
break;
|
||||
|
||||
case nameof(SField.serializable):
|
||||
serializable = reader.ReadAsBoolean();
|
||||
break;
|
||||
|
||||
case nameof(SField.isStatic):
|
||||
isStatic = reader.ReadAsBoolean();
|
||||
break;
|
||||
|
||||
default:
|
||||
reader.Skip(); // Skip unknown properties
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return new SField(declaringType: declaringType, fieldName: fieldName, assemblyName: assemblyName, metadataToken ?? 0, isStatic ?? false, serializable ?? false);
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) {
|
||||
var responses = (List<MethodPatchResponse>)value;
|
||||
@@ -412,6 +532,60 @@ namespace SingularityGroup.HotReload.JsonConverters {
|
||||
}
|
||||
writer.WriteEndArray();
|
||||
}
|
||||
|
||||
if (responsePatch.newFields != null) {
|
||||
writer.WritePropertyName(nameof(responsePatch.newFields));
|
||||
writer.WriteStartArray();
|
||||
foreach (var newField in responsePatch.newFields) {
|
||||
WriteSField(writer, newField);
|
||||
}
|
||||
writer.WriteEndArray();
|
||||
}
|
||||
|
||||
if (responsePatch.deletedFields != null) {
|
||||
writer.WritePropertyName(nameof(responsePatch.deletedFields));
|
||||
writer.WriteStartArray();
|
||||
foreach (var deletedField in responsePatch.deletedFields) {
|
||||
WriteSField(writer, deletedField);
|
||||
}
|
||||
writer.WriteEndArray();
|
||||
}
|
||||
|
||||
if (responsePatch.renamedFieldsFrom != null) {
|
||||
writer.WritePropertyName(nameof(responsePatch.renamedFieldsFrom));
|
||||
writer.WriteStartArray();
|
||||
foreach (var removedFieldFrom in responsePatch.renamedFieldsFrom) {
|
||||
WriteSField(writer, removedFieldFrom);
|
||||
}
|
||||
writer.WriteEndArray();
|
||||
}
|
||||
|
||||
if (responsePatch.renamedFieldsTo != null) {
|
||||
writer.WritePropertyName(nameof(responsePatch.renamedFieldsTo));
|
||||
writer.WriteStartArray();
|
||||
foreach (var removedFieldTo in responsePatch.renamedFieldsTo) {
|
||||
WriteSField(writer, removedFieldTo);
|
||||
}
|
||||
writer.WriteEndArray();
|
||||
}
|
||||
|
||||
if (responsePatch.propertyAttributesFieldOriginal != null) {
|
||||
writer.WritePropertyName(nameof(responsePatch.propertyAttributesFieldOriginal));
|
||||
writer.WriteStartArray();
|
||||
foreach (var removedFieldFrom in responsePatch.propertyAttributesFieldOriginal) {
|
||||
WriteSField(writer, removedFieldFrom);
|
||||
}
|
||||
writer.WriteEndArray();
|
||||
}
|
||||
|
||||
if (responsePatch.propertyAttributesFieldUpdated != null) {
|
||||
writer.WritePropertyName(nameof(responsePatch.propertyAttributesFieldUpdated));
|
||||
writer.WriteStartArray();
|
||||
foreach (var removedFieldTo in responsePatch.propertyAttributesFieldUpdated) {
|
||||
WriteSField(writer, removedFieldTo);
|
||||
}
|
||||
writer.WriteEndArray();
|
||||
}
|
||||
|
||||
writer.WriteEndObject();
|
||||
}
|
||||
@@ -436,6 +610,51 @@ namespace SingularityGroup.HotReload.JsonConverters {
|
||||
writer.WriteEndArray();
|
||||
}
|
||||
|
||||
if (response.alteredFields != null) {
|
||||
writer.WritePropertyName(nameof(response.alteredFields));
|
||||
writer.WriteStartArray();
|
||||
foreach (var alteredField in response.alteredFields) {
|
||||
WriteSField(writer, alteredField);
|
||||
}
|
||||
writer.WriteEndArray();
|
||||
}
|
||||
|
||||
if (response.addedFieldInitializerFields != null) {
|
||||
writer.WritePropertyName(nameof(response.addedFieldInitializerFields));
|
||||
writer.WriteStartArray();
|
||||
foreach (var addedFieldInitializerField in response.addedFieldInitializerFields) {
|
||||
WriteSField(writer, addedFieldInitializerField);
|
||||
}
|
||||
writer.WriteEndArray();
|
||||
}
|
||||
|
||||
if (response.addedFieldInitializerInitializers != null) {
|
||||
writer.WritePropertyName(nameof(response.addedFieldInitializerInitializers));
|
||||
writer.WriteStartArray();
|
||||
foreach (var addedFieldInitializerInitializer in response.addedFieldInitializerInitializers) {
|
||||
WriteSMethod(writer, addedFieldInitializerInitializer);
|
||||
}
|
||||
writer.WriteEndArray();
|
||||
}
|
||||
|
||||
if (response.removedFieldInitializers != null) {
|
||||
writer.WritePropertyName(nameof(response.removedFieldInitializers));
|
||||
writer.WriteStartArray();
|
||||
foreach (var removedFieldInitializer in response.removedFieldInitializers) {
|
||||
WriteSField(writer, removedFieldInitializer);
|
||||
}
|
||||
writer.WriteEndArray();
|
||||
}
|
||||
|
||||
if (response.newFieldDefinitions != null) {
|
||||
writer.WritePropertyName(nameof(response.newFieldDefinitions));
|
||||
writer.WriteStartArray();
|
||||
foreach (var newFieldDefinition in response.newFieldDefinitions) {
|
||||
WriteSField(writer, newFieldDefinition);
|
||||
}
|
||||
writer.WriteEndArray();
|
||||
}
|
||||
|
||||
writer.WriteEndObject();
|
||||
}
|
||||
writer.WriteEndArray();
|
||||
@@ -450,51 +669,48 @@ namespace SingularityGroup.HotReload.JsonConverters {
|
||||
writer.WriteValue(method.displayName);
|
||||
writer.WritePropertyName(nameof(method.metadataToken));
|
||||
writer.WriteValue(method.metadataToken);
|
||||
|
||||
if (method.genericTypeArguments != null) {
|
||||
writer.WritePropertyName(nameof(method.genericTypeArguments));
|
||||
writer.WriteStartArray();
|
||||
foreach (var genericTypeArgument in method.genericTypeArguments) {
|
||||
WriteSType(writer, genericTypeArgument);
|
||||
}
|
||||
writer.WriteEndArray();
|
||||
}
|
||||
|
||||
if (method.genericArguments != null) {
|
||||
writer.WritePropertyName(nameof(method.genericArguments));
|
||||
writer.WriteStartArray();
|
||||
foreach (var genericArgument in method.genericArguments) {
|
||||
WriteSType(writer, genericArgument);
|
||||
}
|
||||
writer.WriteEndArray();
|
||||
}
|
||||
|
||||
writer.WritePropertyName(nameof(method.simpleName));
|
||||
writer.WriteValue(method.simpleName);
|
||||
|
||||
writer.WriteEndObject();
|
||||
}
|
||||
|
||||
void WriteSField(JsonWriter writer, SField field) {
|
||||
writer.WriteStartObject();
|
||||
|
||||
writer.WritePropertyName(nameof(field.declaringType));
|
||||
writer.WriteSType(field.declaringType);
|
||||
writer.WritePropertyName(nameof(field.fieldName));
|
||||
writer.WriteValue(field.fieldName);
|
||||
writer.WritePropertyName(nameof(field.assemblyName));
|
||||
writer.WriteValue(field.assemblyName);
|
||||
writer.WritePropertyName(nameof(field.metadataToken));
|
||||
writer.WriteValue(field.metadataToken);
|
||||
writer.WritePropertyName(nameof(field.serializable));
|
||||
writer.WriteValue(field.serializable);
|
||||
|
||||
writer.WriteEndObject();
|
||||
}
|
||||
|
||||
void WriteSType(JsonWriter writer, SType type) {
|
||||
}
|
||||
internal static class MethodPatchResponsesConverterExtensions {
|
||||
public static void WriteSType(this JsonWriter writer, SType type) {
|
||||
if (type == null) {
|
||||
writer.WriteNull();
|
||||
return;
|
||||
}
|
||||
writer.WriteStartObject();
|
||||
|
||||
writer.WritePropertyName(nameof(type.assemblyName));
|
||||
writer.WriteValue(type.assemblyName);
|
||||
writer.WritePropertyName(nameof(type.typeName));
|
||||
writer.WriteValue(type.typeName);
|
||||
|
||||
// always writing generic arguments will cause recursion issues
|
||||
if (type.genericArguments?.Length > 0) {
|
||||
writer.WritePropertyName(nameof(type.genericArguments));
|
||||
writer.WriteStartArray();
|
||||
foreach (var genericArgument in type.genericArguments) {
|
||||
WriteSType(writer, genericArgument);
|
||||
}
|
||||
writer.WriteEndArray();
|
||||
}
|
||||
writer.WritePropertyName(nameof(type.metadataToken));
|
||||
writer.WriteValue(type.metadataToken);
|
||||
|
||||
writer.WriteEndObject();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -14,8 +14,21 @@ namespace SingularityGroup.HotReload {
|
||||
ptr->monoMethodFlags |= Interop.MonoMethodFlags.skip_visibility;
|
||||
}
|
||||
}
|
||||
|
||||
public static unsafe bool IsMethodInlined(MethodBase method) {
|
||||
if(IntPtr.Size == sizeof(long)) {
|
||||
var ptr = (Interop.MonoMethod64*)method.MethodHandle.Value.ToPointer();
|
||||
return (ptr -> monoMethodFlags & Interop.MonoMethodFlags.inline_info) == Interop.MonoMethodFlags.inline_info;
|
||||
} else {
|
||||
var ptr = (Interop.MonoMethod32*)method.MethodHandle.Value.ToPointer();
|
||||
return (ptr -> monoMethodFlags & Interop.MonoMethodFlags.inline_info) == Interop.MonoMethodFlags.inline_info;
|
||||
}
|
||||
}
|
||||
#else
|
||||
public static void DisableVisibilityChecks(MethodBase method) { }
|
||||
public static bool IsMethodInlined(MethodBase method) {
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,9 @@ using System.Collections;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
#if ENABLE_INPUT_SYSTEM
|
||||
using UnityEngine.InputSystem;
|
||||
#endif
|
||||
|
||||
namespace SingularityGroup.HotReload {
|
||||
internal class Prompts : MonoBehaviour {
|
||||
@@ -105,10 +108,18 @@ namespace SingularityGroup.HotReload {
|
||||
private void Update() {
|
||||
if (!userTriedToInteract) {
|
||||
// when user interacts with the screen, make sure overlay can handle taps
|
||||
#if ENABLE_INPUT_SYSTEM
|
||||
if ((Touchscreen.current != null && Touchscreen.current.touches.Count > 0) ||
|
||||
(Mouse.current != null && Mouse.current.leftButton.wasPressedThisFrame)) {
|
||||
userTriedToInteract = true;
|
||||
DoEnsureEventSystem();
|
||||
}
|
||||
#else
|
||||
if (Input.touchCount > 0 || Input.GetMouseButtonDown(0)) {
|
||||
userTriedToInteract = true;
|
||||
DoEnsureEventSystem();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,10 +8,10 @@ namespace SingularityGroup.HotReload {
|
||||
public static bool IsAssetStoreBuild => true;
|
||||
|
||||
|
||||
public const string Version = "1.12.14";
|
||||
public const string Version = "1.13.7";
|
||||
// Never higher than Version
|
||||
// Used for the download
|
||||
public const string ServerVersion = "1.12.12";
|
||||
public const string ServerVersion = "1.13.7";
|
||||
public const string PackageName = "com.singularitygroup.hotreload";
|
||||
public const string LibraryCachePath = "Library/" + PackageName;
|
||||
public const string ConfigFileName = "hot-reload-config.json";
|
||||
|
||||
@@ -100,7 +100,6 @@ namespace SingularityGroup.HotReload {
|
||||
static void HandleResponseReceived(MethodPatchResponse response) {
|
||||
Log.Debug("PollMethodPatches handling MethodPatchResponse id:{0} response.patches.Length:{1} response.failures.Length:{2}",
|
||||
response.id, response.patches.Length, response.failures.Length);
|
||||
// TODO handle new response data (removed methods etc.)
|
||||
if(response.patches.Length > 0) {
|
||||
CodePatcher.I.RegisterPatches(response, persist: true);
|
||||
}
|
||||
|
||||
@@ -66,6 +66,8 @@ namespace SingularityGroup.HotReload {
|
||||
return;
|
||||
}
|
||||
|
||||
CodePatcher.I.debuggerCompatibilityEnabled = true;
|
||||
|
||||
try {
|
||||
var customIp = PlayerPrefs.GetString("HotReloadRuntime.CustomIP", "");
|
||||
if (!string.IsNullOrEmpty(customIp)) {
|
||||
|
||||
@@ -345,14 +345,41 @@ namespace SingularityGroup.HotReload {
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsReleaseMode() {
|
||||
# if (UNITY_EDITOR && UNITY_2022_1_OR_NEWER)
|
||||
return UnityEditor.Compilation.CompilationPipeline.codeOptimization == UnityEditor.Compilation.CodeOptimization.Release;
|
||||
# elif (UNITY_EDITOR)
|
||||
return false;
|
||||
# elif (DEBUG)
|
||||
return false;
|
||||
# else
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
public static Task RequestClearPatches() {
|
||||
var body = SerializeRequestBody(new CompileRequest(serverInfo.rootPath));
|
||||
var body = SerializeRequestBody(new CompileRequest(serverInfo.rootPath, IsReleaseMode()));
|
||||
return PostJson(url + "/clearpatches", body, 10);
|
||||
}
|
||||
|
||||
public static Task RequestCompile() {
|
||||
var body = SerializeRequestBody(new CompileRequest(serverInfo.rootPath));
|
||||
return PostJson(url + "/compile", body, 10);
|
||||
public static async Task RequestCompile(Action<string> onResponseReceived) {
|
||||
var body = SerializeRequestBody(new CompileRequest(serverInfo.rootPath, IsReleaseMode()));
|
||||
var result = await PostJson(url + "/compile", body, 10);
|
||||
if (result.statusCode == HttpStatusCode.OK && !string.IsNullOrEmpty(result.responseText)) {
|
||||
var responses = JsonConvert.DeserializeObject<List<string>>(result.responseText);
|
||||
if (responses == null) {
|
||||
return;
|
||||
}
|
||||
await ThreadUtility.SwitchToMainThread();
|
||||
foreach (var response in responses) {
|
||||
// Avoid importing assets twice
|
||||
if (responses.Contains(response + ".meta")) {
|
||||
Log.Debug($"Ignoring asset change inside Unity: {response}");
|
||||
continue;
|
||||
}
|
||||
onResponseReceived(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static async Task<List<ChangelogVersion>> FetchChangelog(int timeoutSeconds = 20) {
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
"GUID:2bafac87e7f4b9b418d9448d219b01ab",
|
||||
"GUID:46c537318f0d530469a1df1fafe86c9c",
|
||||
"GUID:af67dfb2ec5c9c740be50ae4470ed85f",
|
||||
"SingularityGroup.HotReload.Runtime.Public"
|
||||
"SingularityGroup.HotReload.Runtime.Public",
|
||||
"Unity.InputSystem"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
|
||||
@@ -23,25 +23,50 @@ namespace SingularityGroup.HotReload {
|
||||
assemblies.Add(asm);
|
||||
}
|
||||
|
||||
public Type Resolve(SType t) {
|
||||
List<Assembly> assemblies;
|
||||
if (assembliesByName.TryGetValue(t.assemblyName, out assemblies)) {
|
||||
|
||||
Type type;
|
||||
foreach (var assembly in assemblies) {
|
||||
if ((type = assembly.GetType(t.typeName)) != null) {
|
||||
if(t.typeName == "System.Array" && t.genericArguments.Length > 0) {
|
||||
var elementType = Resolve(t.genericArguments[0]);
|
||||
return elementType.Assembly.GetType(t.genericArguments[0].typeName + "[]");
|
||||
public Type Resolve(SType t) {
|
||||
var assmeblies = Resolve(t.assemblyName);
|
||||
Type result = null;
|
||||
Exception lastException = null;
|
||||
for (var i = 0; i < assmeblies.Count; i++) {
|
||||
try {
|
||||
result = assmeblies[i].GetLoadedModules()[0].ResolveType(t.metadataToken);
|
||||
if (t.isGenericParameter) {
|
||||
if (!result.IsGenericTypeDefinition) {
|
||||
throw new SymbolResolvingFailedException(t, new ApplicationException("Generic parameter did not resolve to generic type definition"));
|
||||
}
|
||||
if(t.genericArguments.Length > 0) {
|
||||
type = type.MakeGenericType(ResolveTypes(t.genericArguments));
|
||||
var genericParameters = result.GetGenericArguments();
|
||||
if (t.genericParameterPosition >= genericParameters.Length) {
|
||||
throw new SymbolResolvingFailedException(t, new ApplicationException("Generic parameter did not exist on the generic type definition"));
|
||||
}
|
||||
return type;
|
||||
result = genericParameters[t.genericParameterPosition];
|
||||
}
|
||||
break;
|
||||
} catch(Exception ex) {
|
||||
lastException = ex;
|
||||
}
|
||||
}
|
||||
throw new SymbolResolvingFailedException(t);
|
||||
if(result == null) {
|
||||
throw new SymbolResolvingFailedException(t, lastException);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public FieldInfo Resolve(SField t) {
|
||||
var assmeblies = Resolve(t.assemblyName);
|
||||
FieldInfo result = null;
|
||||
Exception lastException = null;
|
||||
for (var i = 0; i < assmeblies.Count; i++) {
|
||||
try {
|
||||
result = assmeblies[i].GetLoadedModules()[0].ResolveField(t.metadataToken);
|
||||
break;
|
||||
} catch(Exception ex) {
|
||||
lastException = ex;
|
||||
}
|
||||
}
|
||||
if(result == null) {
|
||||
throw new SymbolResolvingFailedException(t, lastException);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public IReadOnlyList<Assembly> Resolve(string assembly) {
|
||||
@@ -54,13 +79,11 @@ namespace SingularityGroup.HotReload {
|
||||
|
||||
public MethodBase Resolve(SMethod m) {
|
||||
var assmeblies = Resolve(m.assemblyName);
|
||||
var genericTypeArgs = ResolveTypes(m.genericTypeArguments);
|
||||
var genericMethodArgs = ResolveTypes(m.genericArguments);
|
||||
MethodBase result = null;
|
||||
Exception lastException = null;
|
||||
for (var i = 0; i < assmeblies.Count; i++) {
|
||||
try {
|
||||
result = assmeblies[i].GetLoadedModules()[0].ResolveMethod(m.metadataToken, genericTypeArgs, genericMethodArgs);
|
||||
result = assmeblies[i].GetLoadedModules()[0].ResolveMethod(m.metadataToken);
|
||||
break;
|
||||
} catch(Exception ex) {
|
||||
lastException = ex;
|
||||
@@ -71,20 +94,6 @@ namespace SingularityGroup.HotReload {
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Type[] ResolveTypes(SType[] sTypes) {
|
||||
if(sTypes == null) {
|
||||
return null;
|
||||
}
|
||||
if(sTypes.Length == 0) {
|
||||
return Array.Empty<Type>();
|
||||
}
|
||||
var result = new Type[sTypes.Length];
|
||||
for (int i = 0; i < sTypes.Length; i++) {
|
||||
result[i] = Resolve(sTypes[i]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -7,8 +7,11 @@ namespace SingularityGroup.HotReload {
|
||||
public SymbolResolvingFailedException(SMethod m, Exception inner)
|
||||
: base($"Unable to resolve method {m.displayName} in assembly {m.assemblyName}", inner) { }
|
||||
|
||||
public SymbolResolvingFailedException(SType t)
|
||||
: base($"Unable to resolve type with name: {t.typeName} in assembly {t.assemblyName}") { }
|
||||
public SymbolResolvingFailedException(SType t, Exception inner)
|
||||
: base($"Unable to resolve type with name: {t.typeName} in assembly {t.assemblyName}", inner) { }
|
||||
|
||||
public SymbolResolvingFailedException(SField t, Exception inner)
|
||||
: base($"Unable to resolve field with name: {t.fieldName} in assembly {t.assemblyName}", inner) { }
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user