[Add] Hot Reload

This commit is contained in:
2026-02-27 03:16:18 +07:00
parent 5067cb51a1
commit b37579153b
431 changed files with 43054 additions and 1 deletions
@@ -0,0 +1,53 @@
using System;
using System.Collections;
using UnityEngine;
namespace SingularityGroup.HotReload {
class AppCallbackListener : MonoBehaviour {
/// <summary>
/// Reliable on Android and in the editor.
/// </summary>
/// <remarks>
/// On iOS, OnApplicationPause is not called at expected moments
/// if the app has some background modes enabled in PlayerSettings -Troy.
/// </remarks>
public static event Action<bool> onApplicationPause;
/// <summary>
/// Reliable on Android, iOS and in the editor.
/// </summary>
public static event Action<bool> onApplicationFocus;
static AppCallbackListener instance;
public static AppCallbackListener I => instance;
// Must be called early from Unity main thread (before any usages of the singleton I).
public static AppCallbackListener Init() {
if(instance) return instance;
var go = new GameObject("AppCallbackListener");
go.hideFlags |= HideFlags.HideInHierarchy;
DontDestroyOnLoad(go);
return instance = go.AddComponent<AppCallbackListener>();
}
public bool Paused { get; private set; } = false;
public void DelayedQuit(float seconds) {
StartCoroutine(delayedQuitRoutine(seconds));
}
IEnumerator delayedQuitRoutine(float seconds) {
yield return new WaitForSeconds(seconds);
Application.Quit();
}
void OnApplicationPause(bool paused) {
Paused = paused;
onApplicationPause?.Invoke(paused);
}
void OnApplicationFocus(bool playing) {
onApplicationFocus?.Invoke(playing);
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: a989a17330b04c6fb8f91aa41ac14471
timeCreated: 1674216227
AssetOrigin:
serializedVersion: 1
productId: 254358
packageName: Hot Reload | Edit Code Without Compiling
packageVersion: 1.13.17
assetPath: Packages/com.singularitygroup.hotreload/Runtime/AppCallbackListener.cs
uploadId: 870414
@@ -0,0 +1,174 @@
using System;
using System.Collections.Generic;
using System.IO;
using JetBrains.Annotations;
using SingularityGroup.HotReload.Newtonsoft.Json;
using UnityEngine;
using UnityEngine.Serialization;
namespace SingularityGroup.HotReload {
/// <summary>
/// Information about the Unity Player build.
/// </summary>
/// <remarks>
/// <para>
/// This info is used by the HotReload Server to compile your project in the same way that the Unity Player build was compiled.<br/>
/// For example, when building for Android, Unity sets a bunch of define symbols like UNITY_ANDROID.
/// </para>
/// <para>
/// Information that changes between builds is generated at build-time and put in StreamingAssets/.<br/>
/// This approach means that builds do not need to modify a project file (making file dirty in git). For example,
/// whenever user makes a mono build, the CommitHash changes and we need to regenerate the BuildInfo.
/// </para>
/// </remarks>
[Serializable]
class BuildInfo {
/// <summary>
/// Uniquely identifies the Unity project.
/// </summary>
/// <remarks>
/// Used on-device to check if Hot Reload server is compatible with the Unity project (same project).<br/>
/// When your computer has multiple Unity projects open, each project should provide a different value.<br/>
/// This identifier must also be the same between two different computers that are collaborating on the same project.
///
/// <para>
/// Edge-case: when a user copy pastes an entire Unity project and has both open at once,
/// then it's fine for this identifier to be the same.
/// </para>
/// </remarks>
public string projectIdentifier;
/// <summary>
/// Git commit hash
/// </summary>
/// <remarks>
/// Used to detect that your code is different to when the build was made.
/// </remarks>
public string commitHash;
/// <summary>
/// List of define symbols that were active when this build was made.
/// </summary>
/// <remarks>
/// Separate the symbols with a semi-colon character ';'
/// </remarks>
public string defineSymbols;
/// <summary>
/// A regex of C# project names (*.csproj) to be omitted from compilation.
/// </summary>
/// <example>
/// "MyTests|MyEditorAssembly"
/// </example>
[FormerlySerializedAs("projectExclusionRegex")]
public string projectOmissionRegex;
/// <summary>
/// The computer that made the Android (or Standalone etc) build.<br/>
/// The hostname (ip address) where Hot Reload server would be listening.
/// </summary>
public string buildMachineHostName;
/// <summary>
/// The computer that made the Android (or Standalone etc) build.<br/>
/// The port where Hot Reload server would be listening.
/// </summary>
public int buildMachinePort;
/// <summary>
/// Selected build target in Unity Editor.
/// </summary>
public string activeBuildTarget;
/// <summary>
/// Used to pass in the origin onto the phone which is used to identify the correct server.
/// </summary>
public string buildMachineRequestOrigin;
/// <summary>
/// Used to define which language the package is translated to
/// </summary>
public string locale;
[JsonIgnore]
public HashSet<string> DefineSymbolsAsHashSet {
get {
var symbols = defineSymbols.Trim().Split(';');
// split on an empty string produces 1 empty string
if (symbols.Length == 1 && symbols[0] == string.Empty) {
return new HashSet<string>();
}
return new HashSet<string>(symbols);
}
}
[JsonIgnore]
public PatchServerInfo BuildMachineServer {
get {
if (buildMachineHostName == null || buildMachinePort == 0) {
return null;
}
return new PatchServerInfo(buildMachineHostName, buildMachinePort, commitHash, null, customRequestOrigin: buildMachineRequestOrigin);
}
}
public string ToJson() {
return JsonConvert.SerializeObject(this);
}
[CanBeNull]
public static BuildInfo FromJson(string json) {
if (string.IsNullOrEmpty(json)) {
return null;
}
return JsonConvert.DeserializeObject<BuildInfo>(json);
}
/// <summary>
/// Path to read/write the json file to.
/// </summary>
/// <returns>A filepath that is inside the player build</returns>
public static string GetStoredPath() {
return Path.Combine(Application.streamingAssetsPath, GetStoredName());
}
public static string GetStoredName() {
return "HotReload_BuildInfo.json";
}
/// <returns>True if the commit hashes are definately different, otherwise False</returns>
public bool IsDifferentCommit(string remoteCommit) {
if (commitHash == PatchServerInfo.UnknownCommitHash) {
return false;
}
return !SameCommit(commitHash, remoteCommit);
}
/// <summary>
/// Checks whether the commits are equivalent.
/// </summary>
/// <param name="commitA"></param>
/// <param name="commitB"></param>
/// <returns>False if the commit hashes are definately different, otherwise True</returns>
public static bool SameCommit(string commitA, string commitB) {
if (commitA == null) {
// unknown commit hash, so approve anything
return true;
}
if (commitA.Length == commitB.Length) {
return commitA == commitB;
} else if (commitA.Length >= 6 && commitB.Length >= 6) {
// depending on OS, the git log pretty output has different length (7 or 8 chars)
// if the longer hash starts with the shorter hash, return true
// Assumption: commits have different length.
var longer = commitA.Length > commitB.Length ? commitA : commitB;
var shorter = commitA.Length > commitB.Length ? commitB : commitA;
return longer.StartsWith(shorter);
}
return false;
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 39bb7d4cd9324f31b1882354b1cde762
timeCreated: 1673776105
AssetOrigin:
serializedVersion: 1
productId: 254358
packageName: Hot Reload | Edit Code Without Compiling
packageVersion: 1.13.17
assetPath: Packages/com.singularitygroup.hotreload/Runtime/BuildInfo.cs
uploadId: 870414
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: d10d24dc13744197a80f50ac50f5d1a1
timeCreated: 1675449699
@@ -0,0 +1,25 @@
#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(SUnityJob jobData, Type proxyJobType) {
JobPatchUtility.PatchBurstCompiledJobs(jobData, proxyJobType, unityMajorVersion:
#if UNITY_2022_2_OR_NEWER
2022
#elif UNITY_2021_3_OR_NEWER
2021
#elif UNITY_2020_3_OR_NEWER
2020
#elif UNITY_2019_4_OR_NEWER
2019
#else
2018
#endif
);
}
}
}
#endif
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: b9980b40e3ff447b94e71de238a37fb7
timeCreated: 1676825622
AssetOrigin:
serializedVersion: 1
productId: 254358
packageName: Hot Reload | Edit Code Without Compiling
packageVersion: 1.13.17
assetPath: Packages/com.singularitygroup.hotreload/Runtime/Burst/JobHotReloadUtility.cs
uploadId: 870414
@@ -0,0 +1,39 @@
using System;
using System.Collections.Generic;
using System.Reflection;
namespace SingularityGroup.HotReload {
static class BurstChecker {
//Use names instead of the types directly for compat with older unity versions
const string whitelistAttrName = "BurstCompileAttribute";
const string blacklistAttrName = "BurstDiscardAttribute";
public static bool IsBurstCompiled(MethodBase method) {
//blacklist has precedence over whitelist
if(HasAttr(method.GetCustomAttributes(), blacklistAttrName)) {
return false;
}
if(HasAttr(method.GetCustomAttributes(), whitelistAttrName)) {
return true;
}
//Static methods inside a [BurstCompile] type are not burst compiled by default
if(method.DeclaringType == null || method.IsStatic) {
return false;
}
if(HasAttr(method.DeclaringType.GetCustomAttributes(), whitelistAttrName)) {
return true;
}
//No matching attributes
return false;
}
static bool HasAttr(IEnumerable<Attribute> attributes, string name) {
foreach (var attr in attributes) {
if(attr.GetType().Name == name) {
return true;
}
}
return false;
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 20dfd902e9fc4485aeef90b9add39c0a
timeCreated: 1675404225
AssetOrigin:
serializedVersion: 1
productId: 254358
packageName: Hot Reload | Edit Code Without Compiling
packageVersion: 1.13.17
assetPath: Packages/com.singularitygroup.hotreload/Runtime/BurstChecker.cs
uploadId: 870414
@@ -0,0 +1,770 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using SingularityGroup.HotReload.DTO;
using SingularityGroup.HotReload.Localization;
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;
[assembly: InternalsVisibleTo("SingularityGroup.HotReload.Editor")]
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 bool inspectorFieldAdded;
public readonly List<Tuple<SMethod, string>> patchFailures = new List<Tuple<SMethod, string>>();
public readonly List<string> patchExceptions = new List<string>();
}
class FieldHandler {
public readonly Func<Type, FieldInfo, bool> storeField;
public readonly Action<Type, FieldInfo, FieldInfo> registerInspectorFieldAttributes;
public readonly Func<Type, string, bool> hideField;
public FieldHandler(Func<Type, FieldInfo, bool> storeField, Func<Type, string, bool> hideField, Action<Type, FieldInfo, FieldInfo> registerInspectorFieldAttributes) {
this.storeField = storeField;
this.hideField = hideField;
this.registerInspectorFieldAttributes = registerInspectorFieldAttributes;
}
}
class CodePatcher {
public static readonly CodePatcher I = new CodePatcher();
/// <summary>Tag for use in Debug.Log.</summary>
public const string TAG = "HotReload";
internal int PatchesApplied { get; private set; }
string PersistencePath {get;}
List<MethodPatchResponse> pendingPatches;
readonly List<MethodPatchResponse> patchHistory;
readonly HashSet<string> seenResponses = new HashSet<string>();
string[] assemblySearchPaths;
SymbolResolver symbolResolver;
readonly string tmpDir;
public FieldHandler fieldHandler;
public bool debuggerCompatibilityEnabled;
public bool anyFailures;
public IReadOnlyList<MethodPatchResponse> PatchHistory => patchHistory;
CodePatcher() {
pendingPatches = new List<MethodPatchResponse>();
patchHistory = new List<MethodPatchResponse>();
if(UnityHelper.IsEditor) {
tmpDir = PackageConst.LibraryCachePath;
} else {
tmpDir = UnityHelper.TemporaryCachePath;
}
if(!UnityHelper.IsEditor) {
PersistencePath = Path.Combine(UnityHelper.PersistentDataPath, "HotReload", "patches.json");
try {
LoadPatches(PersistencePath);
} catch(Exception ex) {
Log.Error($"{Localization.Translations.Logging.LoadingPatchesFromDiskError}\n{ex}");
}
} else {
PersistencePath = Path.Combine(PackageConst.LibraryCachePath, "patches.json");
}
#if UNITY_EDITOR
// Unity event methods are not assigned outside the scene.
// So we need to ensure they are added when entering play mode from edit mode
EditorApplication.playModeStateChanged += state => {
if (state != PlayModeStateChange.EnteredPlayMode) {
return;
}
foreach (var unityEventMethod in unityEventMethods) {
EnsureUnityEventMethod(unityEventMethod);
}
};
#endif
}
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
static void InitializeUnityEvents() {
UnityEventHelper.Initialize();
}
void LoadPatches(string filePath) {
PlayerLog(Localization.Translations.Logging.LoadingPatchesFromFile, filePath);
var file = new FileInfo(filePath);
if(file.Exists) {
var bytes = File.ReadAllText(filePath);
var patches = JsonConvert.DeserializeObject<List<MethodPatchResponse>>(bytes);
PlayerLog(Localization.Translations.Logging.LoadedPatchesFromDisk, patches.Count.ToString());
foreach (var patch in patches) {
RegisterPatches(patch, persist: false);
}
}
}
internal IReadOnlyList<MethodPatchResponse> PendingPatches => pendingPatches;
internal SymbolResolver SymbolResolver => symbolResolver;
internal string[] GetAssemblySearchPaths() {
EnsureSymbolResolver();
return assemblySearchPaths;
}
internal void RegisterFailures(MethodPatchResponse patch, RegisterPatchesResult result) {
anyFailures |= patch.failures?.Length > 0 || result?.patchFailures.Count > 0 || result?.patchExceptions.Count > 0;
}
internal RegisterPatchesResult RegisterPatches(MethodPatchResponse patches, bool persist) {
PlayerLog(Localization.Translations.Logging.RegisterPatches, string.Join("\n", patches.failures), string.Join("\n", patches.patches.SelectMany(p => p.modifiedMethods).Select(m => m.displayName)));
pendingPatches.Add(patches);
return ApplyPatches(persist);
}
RegisterPatchesResult ApplyPatches(bool persist) {
PlayerLog(Localization.Translations.Logging.ApplyPatchesPending, pendingPatches.Count);
EnsureSymbolResolver();
var result = new RegisterPatchesResult();
try {
int count = 0;
foreach(var response in pendingPatches) {
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);
seenResponses.Add(response.id);
count += response.patches.Length;
}
if (count > 0) {
Dispatch.OnHotReload(result.patchedMethods).Forget();
}
} catch(Exception ex) {
Log.Warning($"{Localization.Translations.Logging.ExceptionHandlingMethodPatch}\n{ex}");
} finally {
pendingPatches.Clear();
}
if(PersistencePath != null && persist) {
SaveAppliedPatches(PersistencePath).Forget();
}
PatchesApplied++;
return result;
}
internal void ClearPatchedMethods() {
PatchesApplied = 0;
}
static bool didLog;
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)]
static void WarnOnSceneLoad() {
SceneManager.sceneLoaded += (_, __) => {
if (didLog || !UnityEventHelper.UnityMethodsAdded()) {
return;
}
Log.Warning(Localization.Translations.Logging.SceneLoadedWithNewUnityEventMethods);
didLog = true;
};
}
static HashSet<MethodBase> unityEventMethods = new HashSet<MethodBase>();
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)]
static void OnSceneLoad() {
SceneManager.sceneLoaded += (_, __) => {
foreach (var unityEventMethod in unityEventMethods) {
EnsureUnityEventMethod(unityEventMethod);
}
};
}
static bool EnsureUnityEventMethod(MethodBase newMethod) {
try {
return UnityEventHelper.EnsureUnityEventMethod(newMethod);
} catch(Exception ex) {
Log.Warning(Localization.Translations.Logging.ExceptionEnsureUnityEventMethod, ex.GetType().Name, ex.Message);
return false;
}
}
void HandleMethodPatchResponse(MethodPatchResponse response, RegisterPatchesResult result) {
EnsureSymbolResolver();
foreach(var patch in response.patches) {
try {
foreach(var sMethod in patch.newMethods) {
var newMethod = SymbolResolver.Resolve(sMethod);
var isUnityEvent = EnsureUnityEventMethod(newMethod);
if (isUnityEvent) {
unityEventMethods.Add(newMethod);
}
MethodUtils.DisableVisibilityChecks(newMethod);
if (!patch.patchMethods.Any(m => m.metadataToken == sMethod.metadataToken)) {
result.patchedMethods.Add(new MethodPatch(null, null, newMethod));
result.patchedSMethods.Add(sMethod);
previousPatchMethods[newMethod] = newMethod;
newMethods.Add(newMethod);
}
}
for (int i = 0; i < patch.modifiedMethods.Length; i++) {
var sOriginalMethod = patch.modifiedMethods[i];
var sPatchMethod = patch.patchMethods[i];
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));
}
}
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($"{Localization.Translations.Logging.ExceptionApplyingPatch}\nException: {ex}");
}
}
}
void HandleRemovedUnityMethods(SMethod[] removedMethods) {
if (removedMethods == null) {
return;
}
foreach(var sMethod in removedMethods) {
try {
var oldMethod = SymbolResolver.Resolve(sMethod);
UnityEventHelper.RemoveUnityEventMethod(oldMethod);
unityEventMethods.Remove(oldMethod);
} catch (SymbolResolvingFailedException) {
// ignore, not a unity event method if can't resolve
} catch(Exception ex) {
Log.Warning(Localization.Translations.Logging.ExceptionRemoveUnityEventMethod, 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(string.Format(Localization.Translations.Logging.FailedRegisteringInitializerInvalidMethod, sField.fieldName, sField.declaringType.typeName));
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(string.Format(Localization.Translations.Logging.FailedRegisteringInitializerException, sField.fieldName, sField.declaringType.typeName, 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(string.Format(Localization.Translations.Logging.FailedRegisteringNewFieldDefinitions, sField.fieldName, sField.declaringType.typeName, 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(string.Format(Localization.Translations.Logging.FailedRemovingInitializer, sField.fieldName, sField.declaringType.typeName, 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(string.Format(Localization.Translations.Logging.FailedRemovingFieldValue, f.fieldName, f.declaringType.typeName, 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(Localization.Translations.Logging.FailedMovingFieldValue, fromField, toField, toField.declaringType.typeName, 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(string.Format(Localization.Translations.Logging.FailedUpdatingFieldAttributes, original.fieldName, original.declaringType.typeName, 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);
result.inspectorFieldAdded = fieldHandler?.storeField?.Invoke(declaringType, field) ?? false;
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(string.Format(Localization.Translations.Logging.FailedAddingFieldToInspector, sField.fieldName, sField.declaringType.typeName, 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(string.Format(Localization.Translations.Logging.FailedHidingFieldFromInspector, sField.fieldName, sField.declaringType.typeName, 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(string patchId, SMethod sOriginalMethod, SMethod sPatchMethod, bool containsBurstJobs, RegisterPatchesResult patchesResult) {
try {
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 Localization.Translations.Logging.DebuggerAttachedNotAllowed;
}
if (DateTime.UtcNow - start > TimeSpan.FromMilliseconds(500)) {
Log.Info(Localization.Translations.Logging.HotReloadApplyTook, (DateTime.UtcNow - start).TotalMilliseconds);
}
if(state.match == null) {
RequestHelper.RequestEditorEventWithRetry(new Stat(StatSource.Client, StatLevel.Error, StatFeature.Patching, StatEventType.MethodMismatch), new EditorExtraData {
{ StatKey.PatchId, patchId },
}).Forget();
return string.Format(Localization.Translations.Logging.MethodMismatch, sOriginalMethod.simpleName, patchMethod.Name);
}
PlayerLog(Localization.Translations.Logging.DetourMethod, sOriginalMethod.metadataToken, patchMethod.Name, state.offset);
DetourResult result;
DetourApi.DetourMethod(state.match, patchMethod, out result);
if (result.success) {
// previous method is either original method or the last patch method
MethodBase previousMethod;
if (!previousPatchMethods.TryGetValue(state.match, out previousMethod)) {
previousMethod = state.match;
}
MethodBase originalMethod = state.match;
if (newMethods.Contains(state.match)) {
// for function added at runtime the original method should be null
originalMethod = null;
}
patchesResult.patchedMethods.Add(new MethodPatch(originalMethod, previousMethod, patchMethod));
patchesResult.patchedSMethods.Add(sOriginalMethod);
previousPatchMethods[state.match] = patchMethod;
try {
Dispatch.OnHotReloadLocal(state.match, patchMethod);
} catch {
// best effort
}
return null;
} else {
if(result.exception is InvalidProgramException && containsBurstJobs) {
//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);
}
}
struct ResolveMethodState {
public readonly SMethod originalMethod;
public readonly int offset;
public readonly bool tryLowerTokens;
public readonly bool tryHigherTokens;
public readonly MethodBase match;
public ResolveMethodState(SMethod originalMethod, int offset, bool tryLowerTokens, bool tryHigherTokens, MethodBase match) {
this.originalMethod = originalMethod;
this.offset = offset;
this.tryLowerTokens = tryLowerTokens;
this.tryHigherTokens = tryHigherTokens;
this.match = match;
}
public ResolveMethodState With(bool? tryLowerTokens = null, bool? tryHigherTokens = null, MethodBase match = null, int? offset = null) {
return new ResolveMethodState(
originalMethod,
offset ?? this.offset,
tryLowerTokens ?? this.tryLowerTokens,
tryHigherTokens ?? this.tryHigherTokens,
match ?? this.match);
}
}
struct ResolveMethodResult {
public readonly MethodBase resolvedMethod;
public readonly bool tokenOutOfRange;
public ResolveMethodResult(MethodBase resolvedMethod, bool tokenOutOfRange) {
this.resolvedMethod = resolvedMethod;
this.tokenOutOfRange = tokenOutOfRange;
}
}
ResolveMethodState TryResolveMethod(SMethod originalMethod, MethodBase patchMethod) {
var state = new ResolveMethodState(originalMethod, offset: 0, tryLowerTokens: true, tryHigherTokens: true, match: null);
var result = TryResolveMethodCore(state.originalMethod, patchMethod, 0);
if(result.resolvedMethod != null) {
return state.With(match: result.resolvedMethod);
}
state = state.With(offset: 1);
const int tries = 100000;
while(state.offset <= tries && (state.tryHigherTokens || state.tryLowerTokens)) {
if(state.tryHigherTokens) {
result = TryResolveMethodCore(originalMethod, patchMethod, state.offset);
if(result.resolvedMethod != null) {
return state.With(match: result.resolvedMethod);
} else if(result.tokenOutOfRange) {
state = state.With(tryHigherTokens: false);
}
}
if(state.tryLowerTokens) {
result = TryResolveMethodCore(originalMethod, patchMethod, -state.offset);
if(result.resolvedMethod != null) {
return state.With(match: result.resolvedMethod);
} else if(result.tokenOutOfRange) {
state = state.With(tryLowerTokens: false);
}
}
state = state.With(offset: state.offset + 1);
}
return state;
}
ResolveMethodResult TryResolveMethodCore(SMethod methodToResolve, MethodBase patchMethod, int offset) {
bool tokenOutOfRange = false;
MethodBase resolvedMethod = null;
try {
resolvedMethod = TryGetMethodBaseWithRelativeToken(methodToResolve, offset);
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) {
tokenOutOfRange = true;
} catch (ArgumentOutOfRangeException) {
tokenOutOfRange = true;
}
return new ResolveMethodResult(resolvedMethod, tokenOutOfRange);
}
MethodBase TryGetMethodBaseWithRelativeToken(SMethod sOriginalMethod, int offset) {
return symbolResolver.Resolve(new SMethod(sOriginalMethod.assemblyName,
sOriginalMethod.displayName,
sOriginalMethod.metadataToken + offset,
sOriginalMethod.simpleName));
}
string HandleMethodPatchFailure(SMethod method, Exception exception) {
return string.Format(Localization.Translations.Logging.FailedToApplyPatchForMethod, method.displayName, method.assemblyName, exception);
}
void EnsureSymbolResolver() {
if (symbolResolver == null) {
var searchPaths = new HashSet<string>();
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
var assembliesByName = new Dictionary<string, List<Assembly>>();
for (var i = 0; i < assemblies.Length; i++) {
var name = assemblies[i].GetNameSafe();
List<Assembly> list;
if (!assembliesByName.TryGetValue(name, out list)) {
assembliesByName.Add(name, list = new List<Assembly>());
}
list.Add(assemblies[i]);
if(assemblies[i].IsDynamic) continue;
var location = assemblies[i].Location;
if(File.Exists(location)) {
searchPaths.Add(Path.GetDirectoryName(Path.GetFullPath(location)));
}
}
symbolResolver = new SymbolResolver(assembliesByName);
assemblySearchPaths = searchPaths.ToArray();
}
}
//Allow one save operation at a time.
readonly SemaphoreSlim gate = new SemaphoreSlim(1);
public async Task SaveAppliedPatches(string filePath) {
await gate.WaitAsync();
try {
await SaveAppliedPatchesNoLock(filePath);
} finally {
gate.Release();
}
}
async Task SaveAppliedPatchesNoLock(string filePath) {
if (filePath == null) {
throw new ArgumentNullException(nameof(filePath));
}
filePath = Path.GetFullPath(filePath);
var dir = Path.GetDirectoryName(filePath);
if(string.IsNullOrEmpty(dir)) {
throw new ArgumentException(string.Format(Localization.Translations.Logging.InvalidPath, filePath), nameof(filePath));
}
Directory.CreateDirectory(dir);
var history = patchHistory.ToList();
PlayerLog(Localization.Translations.Logging.SavingAppliedPatches, history.Count, filePath);
await Task.Run(() => {
using (FileStream fs = File.Create(filePath))
using (StreamWriter sw = new StreamWriter(fs))
using (JsonWriter writer = new JsonTextWriter(sw)) {
JsonSerializer serializer = JsonSerializer.Create(new JsonSerializerSettings {
Converters = new List<JsonConverter> { new MethodPatchResponsesConverter() }
});
serializer.Serialize(writer, history);
}
});
}
public void InitPatchesBlocked() {
if (PersistencePath == null) {
return;
}
seenResponses.Clear();
var file = new FileInfo(PersistencePath);
if (file.Exists) {
using(var fs = new FileStream(file.FullName, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, FileOptions.SequentialScan))
using (StreamReader sr = new StreamReader(fs))
using (JsonReader reader = new JsonTextReader(sr)) {
JsonSerializer serializer = JsonSerializer.Create(new JsonSerializerSettings {
Converters = new List<JsonConverter> { new MethodPatchResponsesConverter() }
});
pendingPatches = serializer.Deserialize<List<MethodPatchResponse>>(reader);
}
ApplyPatches(persist: false);
}
}
public void ClearPatchesThreaded() {
if (PersistencePath == null) {
return;
}
Task.Run(() => File.Delete(PersistencePath));
}
[StringFormatMethod("format")]
static void PlayerLog(string format, params object[] args) {
#if !UNITY_EDITOR
HotReload.Log.Info(format, args);
#endif //!UNITY_EDITOR
}
class SimpleMethodComparer : IEqualityComparer<SMethod> {
public static readonly SimpleMethodComparer I = new SimpleMethodComparer();
SimpleMethodComparer() { }
public bool Equals(SMethod x, SMethod y) => x.metadataToken == y.metadataToken;
public int GetHashCode(SMethod x) {
return x.metadataToken;
}
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: b6c8477b90c3f384f8124d62a5dc6e74
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 254358
packageName: Hot Reload | Edit Code Without Compiling
packageVersion: 1.13.17
assetPath: Packages/com.singularitygroup.hotreload/Runtime/CodePatcher.cs
uploadId: 870414
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 55206f9d10104e838249bf8ac177e332
timeCreated: 1677091847
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: c895e9065d763824f9211fa8054f7c2e
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: ae744488364b34fcf8c80218eadc721c
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 254358
packageName: Hot Reload | Edit Code Without Compiling
packageVersion: 1.13.17
assetPath: Packages/com.singularitygroup.hotreload/Runtime/Demo/Scenes/HotReloadBasicDemo.unity
uploadId: 870414
@@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: fad9aa54ab3335844b5a35b9eb6ae286
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 254358
packageName: Hot Reload | Edit Code Without Compiling
packageVersion: 1.13.17
assetPath: Packages/com.singularitygroup.hotreload/Runtime/Demo/Scenes/HotReloadBurstDemo.unity
uploadId: 870414
@@ -0,0 +1,66 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!850595691 &4890085278179872738
LightingSettings:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: HotReloadBurstDemoSettings
serializedVersion: 6
m_GIWorkflowMode: 0
m_EnableBakedLightmaps: 1
m_EnableRealtimeLightmaps: 1
m_RealtimeEnvironmentLighting: 1
m_BounceScale: 1
m_AlbedoBoost: 1
m_IndirectOutputScale: 1
m_UsingShadowmask: 1
m_BakeBackend: 1
m_LightmapMaxSize: 1024
m_BakeResolution: 40
m_Padding: 2
m_LightmapCompression: 3
m_AO: 0
m_AOMaxDistance: 1
m_CompAOExponent: 1
m_CompAOExponentDirect: 0
m_ExtractAO: 0
m_MixedBakeMode: 2
m_LightmapsBakeMode: 1
m_FilterMode: 1
m_LightmapParameters: {fileID: 15204, guid: 0000000000000000f000000000000000, type: 0}
m_ExportTrainingData: 0
m_TrainingDataDestination: TrainingData
m_RealtimeResolution: 2
m_ForceWhiteAlbedo: 0
m_ForceUpdates: 0
m_FinalGather: 0
m_FinalGatherRayCount: 256
m_FinalGatherFiltering: 1
m_PVRCulling: 1
m_PVRSampling: 1
m_PVRDirectSampleCount: 32
m_PVRSampleCount: 512
m_PVREnvironmentSampleCount: 512
m_PVREnvironmentReferencePointCount: 2048
m_LightProbeSampleCountMultiplier: 4
m_PVRBounces: 2
m_PVRMinBounces: 2
m_PVREnvironmentImportanceSampling: 0
m_PVRFilteringMode: 2
m_PVRDenoiserTypeDirect: 0
m_PVRDenoiserTypeIndirect: 0
m_PVRDenoiserTypeAO: 0
m_PVRFilterTypeDirect: 0
m_PVRFilterTypeIndirect: 0
m_PVRFilterTypeAO: 0
m_PVRFilteringGaussRadiusDirect: 1
m_PVRFilteringGaussRadiusIndirect: 5
m_PVRFilteringGaussRadiusAO: 2
m_PVRFilteringAtrousPositionSigmaDirect: 0.5
m_PVRFilteringAtrousPositionSigmaIndirect: 2
m_PVRFilteringAtrousPositionSigmaAO: 1
m_PVRTiledBaking: 0
m_NumRaysToShootPerTexel: -1
m_RespectSceneVisibilityWhenBakingGI: 0
@@ -0,0 +1,15 @@
fileFormatVersion: 2
guid: 961e97ae3d4011b47a1198a930f5c30d
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 4890085278179872738
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 254358
packageName: Hot Reload | Edit Code Without Compiling
packageVersion: 1.13.17
assetPath: Packages/com.singularitygroup.hotreload/Runtime/Demo/Scenes/HotReloadBurstDemoSettings.lighting
uploadId: 870414
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 30c72b28fb747184ba79468d3571dea4
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,217 @@
#if ENABLE_MONO && (DEVELOPMENT_BUILD || UNITY_EDITOR)
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
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) {
openWindowButton.onClick.AddListener(Demo.I.OpenHotReloadWindow);
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()) {
informationText.text = Localization.Translations.Common.HotReloadIsRunning;
} else {
informationText.text = Localization.Translations.Common.HotReloadIsNotRunning;
}
// // 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);
// // 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;
// // 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;
// }
}
// 3. Editing lambda methods
static Func<int, int> addFunction = x => {
var result = x + 10;
Debug.Log("Add: " + result);
// // uncomment to change the operator to multiply and log the result
// result = x * 10;
// Debug.Log("Multiply: " + result);
return result;
};
// 4. Editing async/await methods
async Task AsyncMethod() {
// await Task.Delay(500);
// Debug.Log("AsyncMethod");
// // silicense warning
await Task.CompletedTask;
}
// 5. Editing properties (get/set)
public static string SomeString {
// edit the get method
get {
var someStringHere = "This is some string";
return someStringHere;
}
}
// 6. Editing indexers (square bracket access such as dictionaries)
class CustomDictionary : Dictionary<string, int> {
public new int this[string key] {
get {
// // uncomment to change the indexer and log a different entry based on case
// return base[key.ToLower()];
return base[key.ToUpper()];
}
set {
base[key.ToUpper()] = value;
}
}
}
CustomDictionary randomDict = new CustomDictionary {
{ "a", 4 },
{ "A", 5 },
{ "b", 9 },
{ "B", 10 },
{ "c", 14 },
{ "C", 15 },
{ "d", 19 },
{ "D", 20 }
};
// 7. Editing operators methods (explicit and implicit operators)
public class Email {
public string Value { get; }
public Email(string value) {
Value = value;
}
// Define implicit operator
public static implicit operator string(Email value)
// 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);
// Define explicit operator
public static explicit operator Email(string value)
=> 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() {
// // 3. Editing lambda methods
// addFunction(10);
// // 4. Editing async/await methods
// AsyncMethod().Forget();
// // 5. Editing properties (get/set)
// Debug.Log(SomeString);
// // 6. Editing indexers (square bracket access such as dictionaries)
// Debug.Log(randomDict["A"]);
// // 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();
// 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");
}
// // 14. Adding event functions
// void OnDisable() {
// Debug.Log("OnDisable");
// }
}
}
#endif
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 5a2e4d3f095a9441688c70278068eee0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 254358
packageName: Hot Reload | Edit Code Without Compiling
packageVersion: 1.13.17
assetPath: Packages/com.singularitygroup.hotreload/Runtime/Demo/Scripts/HotReloadBasicDemo.cs
uploadId: 870414
@@ -0,0 +1,63 @@
#if ENABLE_MONO && (DEVELOPMENT_BUILD || UNITY_EDITOR)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Jobs;
using UnityEngine.UI;
namespace SingularityGroup.HotReload.Demo {
public class HotReloadBurstJobsDemo : MonoBehaviour {
public Transform[] cubes;
public Text informationText;
public Button openWindowButton;
public Button openScriptButton;
public TextAsset thisScript;
TransformAccessArray cubeTransforms;
CubeJob job;
void Awake() {
cubeTransforms = new TransformAccessArray(cubes);
if(Application.isEditor) {
openWindowButton.onClick.AddListener(Demo.I.OpenHotReloadWindow);
openScriptButton.onClick.AddListener(() => Demo.I.OpenScriptFile(thisScript, 49, 17));
} else {
openWindowButton.gameObject.SetActive(false);
openScriptButton.gameObject.SetActive(false);
}
informationText.gameObject.SetActive(true);
}
void Update() {
job.deltaTime = Time.deltaTime;
job.time = Time.time;
var handle = job.Schedule(cubeTransforms);
handle.Complete();
if (Demo.I.IsServerRunning()) {
informationText.text = Localization.Translations.Common.HotReloadIsRunning;
} else {
informationText.text = Localization.Translations.Common.HotReloadIsNotRunning;
}
}
struct CubeJob : IJobParallelForTransform {
public float deltaTime;
public float time;
public void Execute(int index, TransformAccess transform) {
transform.localRotation *= Quaternion.Euler(50 * deltaTime, 0, 0);
// Uncomment this code to scale the cubes
// var scale = Mathf.Abs(Mathf.Sin(time));
// transform.localScale = new Vector3(scale, scale, scale);
// Uncomment this code to make the cube move from left to right and back
// transform.position += (transform.localScale.x < 0.5 ? Vector3.left : Vector3.right) * deltaTime;
}
}
void OnDestroy() {
cubeTransforms.Dispose();
}
}
}
#endif
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: e09948cf1f317d04fbaf410dbfe91656
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 254358
packageName: Hot Reload | Edit Code Without Compiling
packageVersion: 1.13.17
assetPath: Packages/com.singularitygroup.hotreload/Runtime/Demo/Scripts/HotReloadBurstJobsDemo.cs
uploadId: 870414
@@ -0,0 +1,29 @@
#if ENABLE_MONO && (DEVELOPMENT_BUILD || UNITY_EDITOR)
using UnityEngine;
namespace SingularityGroup.HotReload.Demo {
public interface IDemo {
bool IsServerRunning();
void OpenHotReloadWindow();
void OpenScriptFile(TextAsset textAsset, int line, int column);
}
public static class Demo {
public static IDemo I = new PlayerDemo();
}
public class PlayerDemo : IDemo {
public bool IsServerRunning() {
return ServerHealthCheck.I.IsServerHealthy;
}
public void OpenHotReloadWindow() {
//no-op
}
public void OpenScriptFile(TextAsset textAsset, int line, int column) {
//no-op
}
}
}
#endif
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 04dccdcced0245f1830021fdcad1d28a
timeCreated: 1677321944
AssetOrigin:
serializedVersion: 1
productId: 254358
packageName: Hot Reload | Edit Code Without Compiling
packageVersion: 1.13.17
assetPath: Packages/com.singularitygroup.hotreload/Runtime/Demo/Scripts/IDemo.cs
uploadId: 870414
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: 0dc8d7047b14c44b7970c5d35665dbe1
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 254358
packageName: Hot Reload | Edit Code Without Compiling
packageVersion: 1.13.17
assetPath: Packages/com.singularitygroup.hotreload/Runtime/HotReloadPrompts.prefab
uploadId: 870414
@@ -0,0 +1,43 @@
using System;
using System.IO;
using System.Linq;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace SingularityGroup.HotReload {
internal static class HotReloadSettingsHelper {
public static UnityEngine.GameObject GetOrCreateSettingsPrefab(string prefabAssetPath) {
#if UNITY_EDITOR
var prefab = AssetDatabase.LoadAssetAtPath<UnityEngine.GameObject>(prefabAssetPath);
if (prefab == null) {
// when you use HotReload as a unitypackage, prefab is somewhere inside your assets folder
var guids = AssetDatabase.FindAssets("HotReloadPrompts t:prefab", new string[]{"Assets"});
var paths = guids.Select(guid => AssetDatabase.GUIDToAssetPath(guid));
var promptsPrefabPath = paths.FirstOrDefault(assetpath => Path.GetFileName(assetpath) == "HotReloadPrompts.prefab");
if (promptsPrefabPath != null) {
prefab = AssetDatabase.LoadAssetAtPath<UnityEngine.GameObject>(promptsPrefabPath);
}
}
if (prefab == null) {
throw new Exception(Localization.Translations.Errors.FailedPromptsPrefab);
}
return prefab;
#else
return null;
#endif
}
public static HotReloadSettingsObject GetSettingsObject(string editorAssetPath) {
#if UNITY_EDITOR
return AssetDatabase.LoadAssetAtPath<HotReloadSettingsObject>(editorAssetPath);
#else
return null;
#endif
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 4bff36678f3d4a2bb24c477d28f96888
timeCreated: 1765129558
AssetOrigin:
serializedVersion: 1
productId: 254358
packageName: Hot Reload | Edit Code Without Compiling
packageVersion: 1.13.17
assetPath: Packages/com.singularitygroup.hotreload/Runtime/HotReloadSettingsHelper.cs
uploadId: 870414
@@ -0,0 +1,112 @@
using System;
using JetBrains.Annotations;
using System.IO;
using UnityEngine;
namespace SingularityGroup.HotReload {
/// <summary>
/// HotReload runtime settings. These can be changed while the app is running.
/// </summary>
/// <remarks>
/// ScriptableObject that may be included in Resources/ folder.
/// See also Editor/PrebuildIncludeResources.cs
/// </remarks>
[Serializable]
class HotReloadSettingsObject : ScriptableObject {
#region singleton
private static HotReloadSettingsObject _I;
public static HotReloadSettingsObject I {
get {
if (_I == null) {
_I = LoadSettingsOrDefault();
}
return _I;
}
}
/// <summary>Create settings inside Assets/ because user cannot edit files that are included inside a Unity package</summary>
/// <remarks>
/// You can change this in a build script if you want it created somewhere else.
/// </remarks>
public static string editorAssetPath = "Assets/HotReload/Resources/HotReloadSettingsObject.asset";
private static string resourceName => Path.GetFileNameWithoutExtension(editorAssetPath);
public static bool TryLoadSettings(out HotReloadSettingsObject settings) {
try {
settings = LoadSettings();
return settings != null;
} catch(FileNotFoundException) {
settings = null;
return false;
}
}
[NotNull]
private static HotReloadSettingsObject LoadSettingsOrDefault() {
var settings = LoadSettings();
if (settings == null) {
// load defaults
settings = CreateInstance<HotReloadSettingsObject>();
}
return settings;
}
[CanBeNull]
private static HotReloadSettingsObject LoadSettings() {
HotReloadSettingsObject settings;
if (Application.isEditor) {
settings = HotReloadSettingsHelper.GetSettingsObject(editorAssetPath);
} else {
// load from Resources (assumes that build includes the resource)
settings = Resources.Load<HotReloadSettingsObject>(resourceName);
}
return settings;
}
#endregion
#region settings
/// <summary>Set default values.</summary>
/// <remarks>
/// This is called by the Unity editor when the ScriptableObject is first created.
/// This function is only called in editor mode.
/// </remarks>
private void Reset() {
EnsurePrefabSetCorrectly();
}
/// <summary>
/// Path to the prefab asset file.
/// </summary>
const string prefabAssetPath = "Packages/com.singularitygroup.hotreload/Runtime/HotReloadPrompts.prefab";
// Call this during build, just to be sure the field is correct. (I had some issues with it while editing the prefab)
public void EnsurePrefabSetCorrectly() {
PromptsPrefab = HotReloadSettingsHelper.GetOrCreateSettingsPrefab(prefabAssetPath);
}
public void EnsurePrefabNotInBuild() {
PromptsPrefab = null;
}
// put the stored settings here
[Header(Localization.Translations.MenuItems.BuildSettings)]
[Tooltip(Localization.Translations.MenuItems.IncludeInBuildTooltip)]
public bool IncludeInBuild = true;
[Header(Localization.Translations.MenuItems.PlayerSettings)]
public bool AllowAndroidAppToMakeHttpRequests = false;
#region hidden
/// Reference to the Prefab, for loading it at runtime
[HideInInspector]
public GameObject PromptsPrefab;
#endregion
#endregion settings
}
}
@@ -0,0 +1,20 @@
fileFormatVersion: 2
guid: 324c6fd3c103e0f418eb4b98c46bf63c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences:
- PromptsPrefab: {fileID: 4967086677379066170, guid: 0dc8d7047b14c44b7970c5d35665dbe1,
type: 3}
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 254358
packageName: Hot Reload | Edit Code Without Compiling
packageVersion: 1.13.17
assetPath: Packages/com.singularitygroup.hotreload/Runtime/HotReloadSettingsObject.cs
uploadId: 870414
@@ -0,0 +1,16 @@
using System.Net.Http;
namespace SingularityGroup.HotReload {
public class HttpClientUtils {
public static HttpClient CreateHttpClient() {
var handler = new HttpClientHandler {
// Without this flag HttpClients don't work for PCs with double-byte characters in the name
UseCookies = false
};
return new HttpClient(handler);
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: b40f5d8cac104565b0aaa1d1e294ff8f
timeCreated: 1700069330
AssetOrigin:
serializedVersion: 1
productId: 254358
packageName: Hot Reload | Edit Code Without Compiling
packageVersion: 1.13.17
assetPath: Packages/com.singularitygroup.hotreload/Runtime/HttpClientUtils.cs
uploadId: 870414
@@ -0,0 +1,9 @@
namespace SingularityGroup.HotReload {
public interface IServerHealthCheck {
bool IsServerHealthy { get; }
}
internal interface IServerHealthCheckInternal : IServerHealthCheck {
void CheckHealth();
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: bcb0ff221290427182643b815685ea97
timeCreated: 1675232020
AssetOrigin:
serializedVersion: 1
productId: 254358
packageName: Hot Reload | Edit Code Without Compiling
packageVersion: 1.13.17
assetPath: Packages/com.singularitygroup.hotreload/Runtime/IServerHealthCheck.cs
uploadId: 870414
@@ -0,0 +1,25 @@
using UnityEngine;
using UnityEngine.UI;
namespace SingularityGroup.HotReload {
class InstallQRDialog : MonoBehaviour {
public Button buttonGo;
public Button buttonHide;
private void Start() {
buttonHide.onClick.AddListener(Hide);
// launch camera app that can scan QR-Code https://singularitygroup.atlassian.net/browse/SG-29495
buttonGo.onClick.AddListener(() => {
Hide();
var recommendedQrCodeApp = "com.scanteam.qrcodereader";
Application.OpenURL($"https://play.google.com/store/apps/details?id={recommendedQrCodeApp}");
});
}
/// hide this dialog
void Hide() {
gameObject.SetActive(false); // this should disable the Update loop?
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 03d3be3b485a4450b112f9ea3af4fb66
timeCreated: 1674988075
AssetOrigin:
serializedVersion: 1
productId: 254358
packageName: Hot Reload | Edit Code Without Compiling
packageVersion: 1.13.17
assetPath: Packages/com.singularitygroup.hotreload/Runtime/InstallQRDialog.cs
uploadId: 870414
@@ -0,0 +1,62 @@
#if UNITY_ANDROID && !UNITY_EDITOR
#define MOBILE_ANDROID
#endif
#if UNITY_IOS && !UNITY_EDITOR
#define MOBILE_IOS
#endif
#if MOBILE_ANDROID || MOBILE_IOS
#define MOBILE
#endif
using System;
using System.Net.NetworkInformation;
using System.Net.Sockets;
namespace SingularityGroup.HotReload {
static class IpHelper {
// get my local ip address
static DateTime cachedAt;
static string ipCached;
public static string GetIpAddressCached() {
if (string.IsNullOrEmpty(ipCached) || DateTime.UtcNow - cachedAt > TimeSpan.FromSeconds(5)) {
ipCached = GetIpAddress();
cachedAt = DateTime.UtcNow;
}
return ipCached;
}
public static string GetIpAddress() {
var ip = GetLocalIPv4(NetworkInterfaceType.Wireless80211);
if (string.IsNullOrEmpty(ip)) {
return GetLocalIPv4(NetworkInterfaceType.Ethernet);
}
return ip;
}
private static string GetLocalIPv4(NetworkInterfaceType _type) {
string output = "";
foreach (NetworkInterface item in NetworkInterface.GetAllNetworkInterfaces()) {
if (item.NetworkInterfaceType == _type && item.OperationalStatus == OperationalStatus.Up) {
foreach (UnicastIPAddressInformation ip in item.GetIPProperties().UnicastAddresses) {
if (ip.Address.AddressFamily == AddressFamily.InterNetwork && IsLocalIp(ip.Address.MapToIPv4().GetAddressBytes())) {
output = ip.Address.ToString();
}
}
}
}
return output;
}
// https://datatracker.ietf.org/doc/html/rfc1918#section-3
static bool IsLocalIp(byte[] ipAddress) {
return ipAddress[0] == 10
|| ipAddress[0] == 172
&& ipAddress[1] >= 16
&& ipAddress[1] <= 31
|| ipAddress[0] == 192
&& ipAddress[1] == 168;
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 4d3a24a25ced4eae8b7e0b9b5a0d5c9d
timeCreated: 1674145172
AssetOrigin:
serializedVersion: 1
productId: 254358
packageName: Hot Reload | Edit Code Without Compiling
packageVersion: 1.13.17
assetPath: Packages/com.singularitygroup.hotreload/Runtime/IpHelper.cs
uploadId: 870414
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 053fc5684eb47f54e8c877cb1ade54d6
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 520640393141aab41bd6d6b1f43e7037
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,99 @@
fileFormatVersion: 2
guid: e0277ee5c436c344a9d7720bdc0391d1
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 1
isExplicitlyReferenced: 1
validateReferences: 1
platformData:
- first:
: Any
second:
enabled: 0
settings:
Exclude Android: 1
Exclude Editor: 0
Exclude Linux64: 1
Exclude OSXUniversal: 1
Exclude WebGL: 1
Exclude Win: 1
Exclude Win64: 1
Exclude iOS: 1
- first:
Android: Android
second:
enabled: 0
settings:
CPU: ARMv7
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 1
settings:
CPU: AnyCPU
DefaultValueInitialized: true
OS: AnyOS
- first:
Standalone: Linux64
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: OSXUniversal
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: Win
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: Win64
second:
enabled: 0
settings:
CPU: None
- first:
WebGL: WebGL
second:
enabled: 0
settings: {}
- first:
Windows Store Apps: WindowsStoreApps
second:
enabled: 0
settings:
CPU: AnyCPU
- first:
iPhone: iOS
second:
enabled: 0
settings:
AddToEmbeddedBinaries: false
CPU: AnyCPU
CompileFlags:
FrameworkDependencies:
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 254358
packageName: Hot Reload | Edit Code Without Compiling
packageVersion: 1.13.17
assetPath: Packages/com.singularitygroup.hotreload/Runtime/Libs/EditorOnly/SingularityGroup.HotReload.RuntimeDependencies.dll
uploadId: 870414
@@ -0,0 +1,95 @@
fileFormatVersion: 2
guid: 49b66a954ad81dd4795e880bd63dc4c3
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints:
- UNITY_2019_4_OR_NEWER
isPreloaded: 0
isOverridable: 1
isExplicitlyReferenced: 1
validateReferences: 1
platformData:
- first:
: Any
second:
enabled: 0
settings:
Exclude Android: 1
Exclude Editor: 0
Exclude Linux64: 1
Exclude OSXUniversal: 1
Exclude WebGL: 1
Exclude Win: 1
Exclude Win64: 1
Exclude iOS: 1
- first:
Android: Android
second:
enabled: 0
settings:
CPU: ARMv7
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 1
settings:
CPU: AnyCPU
DefaultValueInitialized: true
OS: AnyOS
- first:
Standalone: Linux64
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: OSXUniversal
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: Win
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: Win64
second:
enabled: 0
settings:
CPU: None
- first:
Windows Store Apps: WindowsStoreApps
second:
enabled: 0
settings:
CPU: AnyCPU
- first:
iPhone: iOS
second:
enabled: 0
settings:
AddToEmbeddedBinaries: false
CPU: AnyCPU
CompileFlags:
FrameworkDependencies:
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 254358
packageName: Hot Reload | Edit Code Without Compiling
packageVersion: 1.13.17
assetPath: Packages/com.singularitygroup.hotreload/Runtime/Libs/EditorOnly/SingularityGroup.HotReload.RuntimeDependencies2019.dll
uploadId: 870414
@@ -0,0 +1,95 @@
fileFormatVersion: 2
guid: 784812c918589424a90509ea34a51da0
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints:
- UNITY_2020_3_OR_NEWER
isPreloaded: 0
isOverridable: 1
isExplicitlyReferenced: 1
validateReferences: 1
platformData:
- first:
: Any
second:
enabled: 0
settings:
Exclude Android: 1
Exclude Editor: 0
Exclude Linux64: 1
Exclude OSXUniversal: 1
Exclude WebGL: 1
Exclude Win: 1
Exclude Win64: 1
Exclude iOS: 1
- first:
Android: Android
second:
enabled: 0
settings:
CPU: ARMv7
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 1
settings:
CPU: AnyCPU
DefaultValueInitialized: true
OS: AnyOS
- first:
Standalone: Linux64
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: OSXUniversal
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: Win
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: Win64
second:
enabled: 0
settings:
CPU: None
- first:
Windows Store Apps: WindowsStoreApps
second:
enabled: 0
settings:
CPU: AnyCPU
- first:
iPhone: iOS
second:
enabled: 0
settings:
AddToEmbeddedBinaries: false
CPU: AnyCPU
CompileFlags:
FrameworkDependencies:
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 254358
packageName: Hot Reload | Edit Code Without Compiling
packageVersion: 1.13.17
assetPath: Packages/com.singularitygroup.hotreload/Runtime/Libs/EditorOnly/SingularityGroup.HotReload.RuntimeDependencies2020.dll
uploadId: 870414
@@ -0,0 +1,95 @@
fileFormatVersion: 2
guid: 8c8658e0b34ecf04ca6ca07ffa6fc846
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints:
- UNITY_2022_2_OR_NEWER
isPreloaded: 0
isOverridable: 1
isExplicitlyReferenced: 1
validateReferences: 1
platformData:
- first:
: Any
second:
enabled: 0
settings:
Exclude Android: 1
Exclude Editor: 0
Exclude Linux64: 1
Exclude OSXUniversal: 1
Exclude WebGL: 1
Exclude Win: 1
Exclude Win64: 1
Exclude iOS: 1
- first:
Android: Android
second:
enabled: 0
settings:
CPU: ARMv7
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 1
settings:
CPU: AnyCPU
DefaultValueInitialized: true
OS: AnyOS
- first:
Standalone: Linux64
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: OSXUniversal
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: Win
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: Win64
second:
enabled: 0
settings:
CPU: None
- first:
Windows Store Apps: WindowsStoreApps
second:
enabled: 0
settings:
CPU: AnyCPU
- first:
iPhone: iOS
second:
enabled: 0
settings:
AddToEmbeddedBinaries: false
CPU: AnyCPU
CompileFlags:
FrameworkDependencies:
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 254358
packageName: Hot Reload | Edit Code Without Compiling
packageVersion: 1.13.17
assetPath: Packages/com.singularitygroup.hotreload/Runtime/Libs/EditorOnly/SingularityGroup.HotReload.RuntimeDependencies2022.dll
uploadId: 870414
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 4b3d6360d6d1f2c47b659f0a4960ebfe
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,95 @@
fileFormatVersion: 2
guid: 15528e9db0a6c9b45a66378f0b6c4dd6
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints:
- ENABLE_MONO
- DEVELOPMENT_BUILD || UNITY_EDITOR
isPreloaded: 0
isOverridable: 1
isExplicitlyReferenced: 1
validateReferences: 1
platformData:
- first:
: Any
second:
enabled: 0
settings:
Exclude Android: 0
Exclude Editor: 1
Exclude Linux64: 0
Exclude OSXUniversal: 0
Exclude Win: 0
Exclude Win64: 0
Exclude iOS: 0
- first:
Android: Android
second:
enabled: 1
settings:
CPU: ARMv7
- first:
Any:
second:
enabled: 1
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
CPU: AnyCPU
DefaultValueInitialized: true
OS: AnyOS
- first:
Standalone: Linux64
second:
enabled: 1
settings:
CPU: AnyCPU
- first:
Standalone: OSXUniversal
second:
enabled: 1
settings:
CPU: x86_64
- first:
Standalone: Win
second:
enabled: 1
settings:
CPU: x86
- first:
Standalone: Win64
second:
enabled: 1
settings:
CPU: x86_64
- first:
Windows Store Apps: WindowsStoreApps
second:
enabled: 0
settings:
CPU: AnyCPU
- first:
iPhone: iOS
second:
enabled: 1
settings:
AddToEmbeddedBinaries: false
CPU: AnyCPU
CompileFlags:
FrameworkDependencies:
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 254358
packageName: Hot Reload | Edit Code Without Compiling
packageVersion: 1.13.17
assetPath: Packages/com.singularitygroup.hotreload/Runtime/Libs/OnDevice/SingularityGroup.HotReload.RuntimeDependencies.dll
uploadId: 870414
@@ -0,0 +1,96 @@
fileFormatVersion: 2
guid: 4febf8334e6a82f4e9faf3513c7fcc8d
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints:
- ENABLE_MONO
- UNITY_2019_4_OR_NEWER
- DEVELOPMENT_BUILD || UNITY_EDITOR
isPreloaded: 0
isOverridable: 1
isExplicitlyReferenced: 1
validateReferences: 1
platformData:
- first:
: Any
second:
enabled: 0
settings:
Exclude Android: 0
Exclude Editor: 1
Exclude Linux64: 0
Exclude OSXUniversal: 0
Exclude Win: 0
Exclude Win64: 0
Exclude iOS: 0
- first:
Android: Android
second:
enabled: 1
settings:
CPU: ARMv7
- first:
Any:
second:
enabled: 1
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
CPU: AnyCPU
DefaultValueInitialized: true
OS: AnyOS
- first:
Standalone: Linux64
second:
enabled: 1
settings:
CPU: AnyCPU
- first:
Standalone: OSXUniversal
second:
enabled: 1
settings:
CPU: x86_64
- first:
Standalone: Win
second:
enabled: 1
settings:
CPU: x86
- first:
Standalone: Win64
second:
enabled: 1
settings:
CPU: x86_64
- first:
Windows Store Apps: WindowsStoreApps
second:
enabled: 0
settings:
CPU: AnyCPU
- first:
iPhone: iOS
second:
enabled: 1
settings:
AddToEmbeddedBinaries: false
CPU: AnyCPU
CompileFlags:
FrameworkDependencies:
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 254358
packageName: Hot Reload | Edit Code Without Compiling
packageVersion: 1.13.17
assetPath: Packages/com.singularitygroup.hotreload/Runtime/Libs/OnDevice/SingularityGroup.HotReload.RuntimeDependencies2019.dll
uploadId: 870414
@@ -0,0 +1,96 @@
fileFormatVersion: 2
guid: 35c3cec01c5230e41bea51d3ac6fcfa1
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints:
- ENABLE_MONO
- UNITY_2020_3_OR_NEWER
- DEVELOPMENT_BUILD || UNITY_EDITOR
isPreloaded: 0
isOverridable: 1
isExplicitlyReferenced: 1
validateReferences: 1
platformData:
- first:
: Any
second:
enabled: 0
settings:
Exclude Android: 0
Exclude Editor: 1
Exclude Linux64: 0
Exclude OSXUniversal: 0
Exclude Win: 0
Exclude Win64: 0
Exclude iOS: 0
- first:
Android: Android
second:
enabled: 1
settings:
CPU: ARMv7
- first:
Any:
second:
enabled: 1
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
CPU: AnyCPU
DefaultValueInitialized: true
OS: AnyOS
- first:
Standalone: Linux64
second:
enabled: 1
settings:
CPU: AnyCPU
- first:
Standalone: OSXUniversal
second:
enabled: 1
settings:
CPU: x86_64
- first:
Standalone: Win
second:
enabled: 1
settings:
CPU: x86
- first:
Standalone: Win64
second:
enabled: 1
settings:
CPU: x86_64
- first:
Windows Store Apps: WindowsStoreApps
second:
enabled: 0
settings:
CPU: AnyCPU
- first:
iPhone: iOS
second:
enabled: 1
settings:
AddToEmbeddedBinaries: false
CPU: AnyCPU
CompileFlags:
FrameworkDependencies:
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 254358
packageName: Hot Reload | Edit Code Without Compiling
packageVersion: 1.13.17
assetPath: Packages/com.singularitygroup.hotreload/Runtime/Libs/OnDevice/SingularityGroup.HotReload.RuntimeDependencies2020.dll
uploadId: 870414
@@ -0,0 +1,96 @@
fileFormatVersion: 2
guid: c9f10603236554c4896f310072d57f24
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints:
- ENABLE_MONO
- UNITY_2022_2_OR_NEWER
- DEVELOPMENT_BUILD || UNITY_EDITOR
isPreloaded: 0
isOverridable: 1
isExplicitlyReferenced: 1
validateReferences: 1
platformData:
- first:
: Any
second:
enabled: 0
settings:
Exclude Android: 0
Exclude Editor: 1
Exclude Linux64: 0
Exclude OSXUniversal: 0
Exclude Win: 0
Exclude Win64: 0
Exclude iOS: 0
- first:
Android: Android
second:
enabled: 1
settings:
CPU: ARMv7
- first:
Any:
second:
enabled: 1
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
CPU: AnyCPU
DefaultValueInitialized: true
OS: AnyOS
- first:
Standalone: Linux64
second:
enabled: 1
settings:
CPU: AnyCPU
- first:
Standalone: OSXUniversal
second:
enabled: 1
settings:
CPU: x86_64
- first:
Standalone: Win
second:
enabled: 1
settings:
CPU: x86
- first:
Standalone: Win64
second:
enabled: 1
settings:
CPU: x86_64
- first:
Windows Store Apps: WindowsStoreApps
second:
enabled: 0
settings:
CPU: AnyCPU
- first:
iPhone: iOS
second:
enabled: 1
settings:
AddToEmbeddedBinaries: false
CPU: AnyCPU
CompileFlags:
FrameworkDependencies:
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 254358
packageName: Hot Reload | Edit Code Without Compiling
packageVersion: 1.13.17
assetPath: Packages/com.singularitygroup.hotreload/Runtime/Libs/OnDevice/SingularityGroup.HotReload.RuntimeDependencies2022.dll
uploadId: 870414
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 392d4a942d5d4d26872be33e375c8d32
timeCreated: 1759652490
@@ -0,0 +1,46 @@
#if ENABLE_MONO && (DEVELOPMENT_BUILD || UNITY_EDITOR)
namespace SingularityGroup.HotReload.Localization {
internal static partial class Translations {
public static class Common {
public static string UnknownException;
public static string HotReloadIsRunning;
public static string HotReloadIsNotRunning;
public static string UnableToResolveMethod;
public static string UnableToResolveType;
public static string UnableToResolveField;
public static string HotReloadUnreachable;
public static string TryingToReconnect;
public static string Disconnected;
public static string Unknown;
public static void LoadEnglish() {
UnknownException = "unknown exception";
HotReloadIsRunning = "Hot Reload is running";
HotReloadIsNotRunning = "Hot Reload is not running";
UnableToResolveMethod = "Unable to resolve method";
UnableToResolveType = "Unable to resolve type";
UnableToResolveField = "Unable to resolve field";
HotReloadUnreachable = "Hot Reload was unreachable for 5 seconds, trying to reconnect...";
TryingToReconnect = "Trying to reconnect...";
Disconnected = "Disconnected";
Unknown = "unknown";
}
public static void LoadSimplifiedChinese() {
UnknownException = "未知异常";
HotReloadIsRunning = "Hot Reload 正在运行";
HotReloadIsNotRunning = "Hot Reload 未运行";
UnableToResolveMethod = "无法解析方法";
UnableToResolveType = "无法解析类型";
UnableToResolveField = "无法解析字段";
HotReloadUnreachable = "Hot Reload 5 秒内无法访问,正在尝试重新连接...";
TryingToReconnect = "正在尝试重新连接...";
Disconnected = "已断开连接";
Unknown = "未知";
}
}
}
}
#endif
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 71c2ede153664ef48b2919d3c42da1a3
timeCreated: 1762538401
AssetOrigin:
serializedVersion: 1
productId: 254358
packageName: Hot Reload | Edit Code Without Compiling
packageVersion: 1.13.17
assetPath: Packages/com.singularitygroup.hotreload/Runtime/Localization/CommonTranslations.cs
uploadId: 870414
@@ -0,0 +1,82 @@
#if ENABLE_MONO && (DEVELOPMENT_BUILD || UNITY_EDITOR)
namespace SingularityGroup.HotReload.Localization {
internal static partial class Translations {
public static class Dialogs {
public static string Information;
public static string ContinueButtonText;
public static string CancelButtonText;
public static string DifferentProjectSummary;
public static string DifferentProjectSuggestion;
public static string DifferentCommitSummary;
public static string DifferentCommitSuggestion;
public static string ConnectionStateConnecting;
public static string ConnectionStateHandshaking;
public static string ConnectionStateDifferencesFound;
public static string ConnectionStateConnected;
public static string ConnectionStateCancelled;
public static string Patches;
public static string IsConnected;
public static string NoWiFiNetwork;
public static string WaitForCompiling;
public static string TargetNetworkIsReachable;
public static string AutoPairEncounteredIssue;
public static string ConnectionFailed;
public static string TryingToReconnect;
public static string Disconnected;
public static string PatchesStatus;
public static void LoadEnglish() {
Information = "Information";
ContinueButtonText = "Continue";
CancelButtonText = "Cancel";
DifferentProjectSummary = "Hot Reload was started from a different project";
DifferentProjectSuggestion = "Please run Hot Reload from the matching Unity project";
DifferentCommitSummary = "Editor and current build are on different commits";
DifferentCommitSuggestion = "This can cause errors when the build was made on an old commit.";
ConnectionStateConnecting = "Connecting ...";
ConnectionStateHandshaking = "Handshaking ...";
ConnectionStateDifferencesFound = "Differences found";
ConnectionStateConnected = "Connected!";
ConnectionStateCancelled = "Cancelled";
Patches = "Patches";
IsConnected = "Is this device connected to {0}?";
NoWiFiNetwork = "WiFi";
WaitForCompiling = "Wait for compiling to finish before trying again";
TargetNetworkIsReachable = "Make sure you're on the same {0} network. Also ensure Hot Reload is running";
AutoPairEncounteredIssue = "Auto-pair encountered an issue";
ConnectionFailed = "Connection failed";
TryingToReconnect = "Trying to reconnect ...";
Disconnected = "Disconnected";
PatchesStatus = "Patches: {0} pending, {1} applied";
}
public static void LoadSimplifiedChinese() {
Information = "信息";
ContinueButtonText = "继续";
CancelButtonText = "取消";
DifferentProjectSummary = "Hot Reload 从不同的项目启动";
DifferentProjectSuggestion = "请从匹配的 Unity 项目运行 Hot Reload";
DifferentCommitSummary = "编辑器和当前构建在不同的提交上";
DifferentCommitSuggestion = "当构建是在旧的提交上进行时,这可能会导致错误。";
ConnectionStateConnecting = "正在连接 ...";
ConnectionStateHandshaking = "正在握手 ...";
ConnectionStateDifferencesFound = "发现差异";
ConnectionStateConnected = "已连接!";
ConnectionStateCancelled = "已取消";
Patches = "补丁";
IsConnected = "此设备是否已连接到 {0}";
NoWiFiNetwork = "WiFi";
WaitForCompiling = "请等待编译完成后再试";
TargetNetworkIsReachable = "请确保您在同一个 {0} 网络中。还要确保 Hot Reload 正在运行";
AutoPairEncounteredIssue = "自动配对遇到问题";
ConnectionFailed = "连接失败";
TryingToReconnect = "正在尝试重新连接 ...";
Disconnected = "已断开连接";
PatchesStatus = "补丁:{0} 待处理,{1} 已应用";
}
}
}
}
#endif
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 3970cf8b2b8a47dd9d16ce5051375690
timeCreated: 1762538420
AssetOrigin:
serializedVersion: 1
productId: 254358
packageName: Hot Reload | Edit Code Without Compiling
packageVersion: 1.13.17
assetPath: Packages/com.singularitygroup.hotreload/Runtime/Localization/DialogTranslations.cs
uploadId: 870414
@@ -0,0 +1,73 @@
#if ENABLE_MONO && (DEVELOPMENT_BUILD || UNITY_EDITOR)
namespace SingularityGroup.HotReload.Localization {
internal static partial class Translations {
public static class Errors {
public static string MethodNameMismatch;
public static string DeclaringTypeNameMismatch;
public static string IsGenericMethodDefinitionMismatch;
public static string MissingThisParameter;
public static string ThisParameterTypeMismatch;
public static string ParameterCountMismatch;
public static string ParameterTypeMismatch;
public static string ReturnTypeMismatch;
public static string GenericParameterNotGenericType;
public static string GenericParameterDidNotExist;
public static string IsPlayerWithHotReloadFalse;
public static string UnknownExceptionReadingBuildInfo;
public static string BuildInfoNotFound;
public static string FailedPromptsPrefab;
public static string HandshakeFailedInvalidBuildTarget;
public static string BuildTargetMismatch;
public static string UnableToResolveMethodInAssembly;
public static string UnableToResolveTypeInAssembly;
public static string UnableToResolveFieldInAssembly;
public static void LoadEnglish() {
MethodNameMismatch = "Method name mismatch";
DeclaringTypeNameMismatch = "Declaring type name mismatch";
IsGenericMethodDefinitionMismatch = "IsGenericMethodDefinition mismatch";
MissingThisParameter = "missing this parameter";
ThisParameterTypeMismatch = "this parameter type mismatch";
ParameterCountMismatch = "parameter count mismatch";
ParameterTypeMismatch = "parameter type mismatch";
ReturnTypeMismatch = "Return type mismatch";
GenericParameterNotGenericType = "Generic parameter did not resolve to generic type definition";
GenericParameterDidNotExist = "Generic parameter did not exist on the generic type definition";
IsPlayerWithHotReloadFalse = "IsPlayerWithHotReload() is false";
UnknownExceptionReadingBuildInfo = "Uknown exception happened when reading build info";
BuildInfoNotFound = "Uknown issue happened when reading build info.";
FailedPromptsPrefab = "Failed to find PromptsPrefab (are you using Hot Reload as a package?";
HandshakeFailedInvalidBuildTarget = "Server did not declare its current Unity activeBuildTarget in the handshake response. Will assume it is {0}.";
BuildTargetMismatch = "Your Unity project is running on {0}. You may need to switch it to {1} for Hot Reload to work.";
UnableToResolveMethodInAssembly = "Unable to resolve method {0} in assembly {1}";
UnableToResolveTypeInAssembly = "Unable to resolve type with name: {0} in assembly {1}";
UnableToResolveFieldInAssembly = "Unable to resolve field with name: {0} in assembly {1}";
}
public static void LoadSimplifiedChinese() {
MethodNameMismatch = "方法名称不匹配";
DeclaringTypeNameMismatch = "声明类型名称不匹配";
IsGenericMethodDefinitionMismatch = "IsGenericMethodDefinition 不匹配";
MissingThisParameter = "缺少 this 参数";
ThisParameterTypeMismatch = "this 参数类型不匹配";
ParameterCountMismatch = "参数数量不匹配";
ParameterTypeMismatch = "参数类型不匹配";
ReturnTypeMismatch = "返回类型不匹配";
GenericParameterNotGenericType = "泛型参数未解析为泛型类型定义";
GenericParameterDidNotExist = "泛型参数在泛型类型定义上不存在";
IsPlayerWithHotReloadFalse = "IsPlayerWithHotReload() 为 false";
UnknownExceptionReadingBuildInfo = "读取构建信息时发生未知异常";
BuildInfoNotFound = "读取构建信息时发生未知问题。";
FailedPromptsPrefab = "未能找到 PromptsPrefab(您是否将 Hot Reload 作为软件包使用?";
HandshakeFailedInvalidBuildTarget = "服务器在握手响应中未声明其当前的 Unity activeBuildTarget。将假定为 {0}。";
BuildTargetMismatch = "您的 Unity 项目正在 {0} 上运行。您可能需要将其切换到 {1} 才能使 Hot Reload 工作。";
UnableToResolveMethodInAssembly = "无法在程序集 {1} 中解析方法 {0}";
UnableToResolveTypeInAssembly = "无法在程序集 {1} 中解析名称为 {0} 的类型";
UnableToResolveFieldInAssembly = "无法在程序集 {1} 中解析名称为 {0} 的字段";
}
}
}
}
#endif
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: c96d2f0898524320839b84cf22fcd820
timeCreated: 1762538447
AssetOrigin:
serializedVersion: 1
productId: 254358
packageName: Hot Reload | Edit Code Without Compiling
packageVersion: 1.13.17
assetPath: Packages/com.singularitygroup.hotreload/Runtime/Localization/ErrorTranslations.cs
uploadId: 870414
@@ -0,0 +1,178 @@
#if ENABLE_MONO && (DEVELOPMENT_BUILD || UNITY_EDITOR)
namespace SingularityGroup.HotReload.Localization {
internal static partial class Translations {
public static class Logging {
// Server and Connection
public static string HotReloadUnreachableDisconnecting;
public static string RequestHandshakeToServer;
public static string ServerHealthyAfterHandshake;
// Polling Errors
public static string PollMethodPatchesFailed;
public static string PollPatchStatusFailed;
public static string PollAssetChangesFailed;
// Request Errors
public static string DeserializingResponseFailed;
public static string RequestTimeout;
// Method Invocation
public static string InvokeOnHotReloadFailed;
public static string InvokeOnHotReloadLocalFailed;
// Build and Player
public static string HotReloadNotAvailableBuildSettings;
public static string BuildInfoNotFound;
// Method Compatibility
public static string UnknownIssue;
// Patch Loading/Saving
public static string LoadingPatchesFromDiskError;
public static string LoadingPatchesFromFile;
public static string LoadedPatchesFromDisk;
public static string SavingAppliedPatches;
// Patch Registration/Application
public static string RegisterPatches;
public static string ApplyPatchesPending;
public static string DetourMethod;
// Exceptions
public static string ExceptionHandlingMethodPatch;
public static string ExceptionApplyingPatch;
public static string ExceptionEnsureUnityEventMethod;
public static string ExceptionRemoveUnityEventMethod;
public static string InvalidPath;
// Field Operations
public static string FailedRegisteringInitializerInvalidMethod;
public static string FailedRegisteringInitializerException;
public static string FailedRegisteringNewFieldDefinitions;
public static string FailedRemovingInitializer;
public static string FailedRemovingFieldValue;
public static string FailedMovingFieldValue;
public static string FailedUpdatingFieldAttributes;
public static string FailedAddingFieldToInspector;
public static string FailedHidingFieldFromInspector;
// Method Patching
public static string DebuggerAttachedNotAllowed;
public static string MethodMismatch;
public static string FailedToApplyPatchForMethod;
public static string HotReloadApplyTook;
// Unity Events
public static string SceneLoadedWithNewUnityEventMethods;
public static void LoadEnglish() {
HotReloadUnreachableDisconnecting = "Hot Reload was unreachable for {0} seconds, disconnecting";
RequestHandshakeToServer = "Request handshake to Hot Reload server with hostname: {0}";
ServerHealthyAfterHandshake = "Server is healthy after first handshake? {0}";
PollMethodPatchesFailed = "PollMethodPatches failed with code {0} {1} {2}";
PollPatchStatusFailed = "PollPatchStatus failed with code {0} {1} {2}";
PollAssetChangesFailed = "PollAssetChanges failed with code {0} {1} {2}";
DeserializingResponseFailed = "Deserializing response failed with {0}: {1}";
RequestTimeout = "Request timeout";
InvokeOnHotReloadFailed = "[InvokeOnHotReload] {0} {1} failed. Exception:\n{2}";
InvokeOnHotReloadLocalFailed = "[InvokeOnHotReloadLocal] {0} {1} failed. Exception:\n{2}";
HotReloadNotAvailableBuildSettings = "Hot Reload is not available in this build because one or more build settings were not supported.";
BuildInfoNotFound = "Build info not found";
UnknownIssue = "unknown issue";
LoadingPatchesFromDiskError = "Encountered exception when loading patches from disk:";
LoadingPatchesFromFile = "Loading patches from file {0}";
LoadedPatchesFromDisk = "Loaded {0} patches from disk";
SavingAppliedPatches = "Saving {0} applied patches to {1}";
RegisterPatches = "Register patches.\nWarnings: {0} \nMethods:\n{1}";
ApplyPatchesPending = "ApplyPatches. {0} patches pending.";
DetourMethod = "Detour method {0:X8} {1}, offset: {2}";
ExceptionHandlingMethodPatch = "Exception occured when handling method patch. Exception:";
ExceptionApplyingPatch = "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.";
ExceptionEnsureUnityEventMethod = "Encountered exception in EnsureUnityEventMethod: {0} {1}";
ExceptionRemoveUnityEventMethod = "Encountered exception in RemoveUnityEventMethod: {0} {1}";
InvalidPath = "Invalid path: {0}";
FailedRegisteringInitializerInvalidMethod = "Failed registering initializer for field {0} in {1}. Field value might not be initialized correctly. Invalid method.";
FailedRegisteringInitializerException = "Failed registering initializer for field {0} in {1}. Field value might not be initialized correctly. Exception: {2}";
FailedRegisteringNewFieldDefinitions = "Failed registering new field definitions for field {0} in {1}. Exception: {2}";
FailedRemovingInitializer = "Failed removing initializer for field {0} in {1}. Field value might not be initialized correctly. Exception: {2}";
FailedRemovingFieldValue = "Failed removing field value from {0} in {1}. Field value in code might not be up to date. Exception: {2}";
FailedMovingFieldValue = "Failed moving field value from {0} to {1} in {2}. Field value in code might not be up to date. Exception: {3}";
FailedUpdatingFieldAttributes = "Failed updating field attributes of {0} in {1}. Updates might not reflect in the inspector. Exception: {2}";
FailedAddingFieldToInspector = "Failed adding field {0}:{1} to the inspector. Field will not be displayed. Exception: {2}";
FailedHidingFieldFromInspector = "Failed hiding field {0}:{1} from the inspector. Exception: {2}";
DebuggerAttachedNotAllowed = "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.";
MethodMismatch = "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.";
FailedToApplyPatchForMethod = "Edit requires full recompile to apply: Failed to apply patch for method {0} in assembly {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.\nException: {2}";
HotReloadApplyTook = "Hot Reload apply took {0}";
SceneLoadedWithNewUnityEventMethods = "A new Scene was loaded while new unity event methods were added at runtime. MonoBehaviours in the Scene will not trigger these new events.";
}
public static void LoadSimplifiedChinese() {
HotReloadUnreachableDisconnecting = "Hot Reload {0} 秒内无法访问,正在断开连接";
RequestHandshakeToServer = "向 Hot Reload 服务器请求握手,主机名:{0}";
ServerHealthyAfterHandshake = "第一次握手后服务器是否健康?{0}";
PollMethodPatchesFailed = "PollMethodPatches 失败,代码 {0} {1} {2}";
PollPatchStatusFailed = "PollPatchStatus 失败,代码 {0} {1} {2}";
PollAssetChangesFailed = "PollAssetChanges 失败,代码 {0} {1} {2}";
DeserializingResponseFailed = "反序列化响应失败,{0}{1}";
RequestTimeout = "请求超时";
InvokeOnHotReloadFailed = "[InvokeOnHotReload] {0} {1} 失败。异常:\n{2}";
InvokeOnHotReloadLocalFailed = "[InvokeOnHotReloadLocal] {0} {1} 失败。异常:\n{2}";
HotReloadNotAvailableBuildSettings = "由于一个或多个构建设置不受支持,Hot Reload 在此构建中不可用。";
BuildInfoNotFound = "未找到构建信息";
UnknownIssue = "未知问题";
LoadingPatchesFromDiskError = "从磁盘加载补丁时遇到异常:";
LoadingPatchesFromFile = "从文件 {0} 加载补丁";
LoadedPatchesFromDisk = "从磁盘加载了 {0} 个补丁";
SavingAppliedPatches = "将 {0} 个已应用的补丁保存到 {1}";
RegisterPatches = "注册补丁。\n警告:{0} \n方法:\n{1}";
ApplyPatchesPending = "ApplyPatches。{0} 个补丁待处理。";
DetourMethod = "Detour 方法 {0:X8} {1},偏移量:{2}";
ExceptionHandlingMethodPatch = "处理方法补丁时发生异常。异常:";
ExceptionApplyingPatch = "编辑需要完全重新编译才能应用:应用补丁时遇到异常。\n常见原因:编辑之前修补失败的代码、不支持的更改或 Hot Reload 中的真正错误。\n如果您认为这是一个错误,请在 Discord 上报告问题并附上之前/之后的代码片段。";
ExceptionEnsureUnityEventMethod = "在 EnsureUnityEventMethod 中遇到异常:{0} {1}";
ExceptionRemoveUnityEventMethod = "在 RemoveUnityEventMethod 中遇到异常:{0} {1}";
InvalidPath = "无效路径:{0}";
FailedRegisteringInitializerInvalidMethod = "在 {1} 中为字段 {0} 注册初始化程序失败。字段值可能未正确初始化。方法无效。";
FailedRegisteringInitializerException = "在 {1} 中为字段 {0} 注册初始化程序失败。字段值可能未正确初始化。异常:{2}";
FailedRegisteringNewFieldDefinitions = "在 {1} 中为字段 {0} 注册新字段定义失败。异常:{2}";
FailedRemovingInitializer = "在 {1} 中为字段 {0} 删除初始化程序失败。字段值可能未正确初始化。异常:{2}";
FailedRemovingFieldValue = "从 {1} 中的 {0} 删除字段值失败。代码中的字段值可能不是最新的。异常:{2}";
FailedMovingFieldValue = "在 {2} 中将字段值从 {0} 移动到 {1} 失败。代码中的字段值可能不是最新的。异常:{3}";
FailedUpdatingFieldAttributes = "在 {1} 中更新 {0} 的字段属性失败。更新可能不会反映在检查器中。异常:{2}";
FailedAddingFieldToInspector = "将字段 {0}:{1} 添加到检查器失败。字段将不会显示。异常:{2}";
FailedHidingFieldFromInspector = "从检查器中隐藏字段 {0}:{1} 失败。异常:{2}";
DebuggerAttachedNotAllowed = "附加调试器时不允许修补方法。如果 Hot Reload 与您正在运行的调试器兼容,您可以在设置中更改此行为。";
MethodMismatch = "编辑需要完全重新编译才能应用:方法不匹配:{0},补丁:{1}。\n常见原因:编辑之前修补失败的代码、不支持的更改或 Hot Reload 中的真正错误。\n如果您认为这是一个错误,请在 Discord 上报告问题并附上之前/之后的代码片段。";
FailedToApplyPatchForMethod = "编辑需要完全重新编译才能应用:为程序集 {1} 中的方法 {0} 应用补丁失败。\n常见原因:编辑之前修补失败的代码、不支持的更改或 Hot Reload 中的真正错误。\n如果您认为这是一个错误,请在 Discord 上报告问题并附上之前/之后的代码片段。\n异常:{2}";
HotReloadApplyTook = "Hot Reload 应用耗时 {0}";
SceneLoadedWithNewUnityEventMethods = "在运行时添加新的 unity 事件方法时加载了新场景。场景中的 MonoBehaviours 不会触发这些新事件。";
}
}
}
}
#endif
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: bbabe74466b6cb84bb5d2e98d9779397
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 254358
packageName: Hot Reload | Edit Code Without Compiling
packageVersion: 1.13.17
assetPath: Packages/com.singularitygroup.hotreload/Runtime/Localization/LoggingTranslations.cs
uploadId: 870414
@@ -0,0 +1,16 @@
#if ENABLE_MONO && (DEVELOPMENT_BUILD || UNITY_EDITOR)
namespace SingularityGroup.HotReload.Localization {
internal static partial class Translations {
public static class MenuItems {
public const string UIControls = PackageConst.DefaultLocale == Locale.SimplifiedChinese ? "UI 控件" : "UI controls";
public const string Information = PackageConst.DefaultLocale == Locale.SimplifiedChinese ? "信息" : "Information";
public const string Other = PackageConst.DefaultLocale == Locale.SimplifiedChinese ? "其他" : "Other";
public const string FalllbackEventSystem = PackageConst.DefaultLocale == Locale.SimplifiedChinese ? "当项目未能及早创建 EventSystem 时使用" : "Used when project does not create an EventSystem early enough";
public const string BuildSettings = PackageConst.DefaultLocale == Locale.SimplifiedChinese ? "构建设置" : "Build Settings";
public const string IncludeInBuildTooltip = PackageConst.DefaultLocale == Locale.SimplifiedChinese ? "Hot Reload 运行时是否应包含在开发版本中?HotReload 永远不会包含在发布版本中。" : "Should the Hot Reload runtime be included in development builds? HotReload is never included in release builds.";
public const string PlayerSettings = PackageConst.DefaultLocale == Locale.SimplifiedChinese ? "播放器设置" : "Player Settings";
}
}
}
#endif
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 12be1a0b0b06402da21c46ae29d60746
timeCreated: 1762675925
AssetOrigin:
serializedVersion: 1
productId: 254358
packageName: Hot Reload | Edit Code Without Compiling
packageVersion: 1.13.17
assetPath: Packages/com.singularitygroup.hotreload/Runtime/Localization/MenuItemTranslations.cs
uploadId: 870414
@@ -0,0 +1,34 @@
#if ENABLE_MONO && (DEVELOPMENT_BUILD || UNITY_EDITOR)
namespace SingularityGroup.HotReload.Localization {
internal static partial class Translations {
public static class Settings {
public static string BuildSettings;
public static string IncludeInBuildTooltip;
public static string PlayerSettings;
public static string Other;
public static string FallbackEventSystemTooltip;
public static string NoEventSystemWarning;
public static void LoadEnglish() {
BuildSettings = "Build Settings";
IncludeInBuildTooltip = "Should the Hot Reload runtime be included in development builds? HotReload is never included in release builds.";
PlayerSettings = "Player Settings";
Other = "Other";
FallbackEventSystemTooltip = "Used when project does not create an EventSystem early enough";
NoEventSystemWarning = "No EventSystem is active, enabling an EventSystem inside Hot Reload {0} prefab. A Unity EventSystem and an Input module is required for tapping buttons on the Unity UI.";
}
public static void LoadSimplifiedChinese() {
BuildSettings = "构建设置";
IncludeInBuildTooltip = "Hot Reload 运行时是否应包含在开发版本中?HotReload 永远不会包含在发布版本中。";
PlayerSettings = "播放器设置";
Other = "其他";
FallbackEventSystemTooltip = "当项目未能及早创建 EventSystem 时使用";
NoEventSystemWarning = "没有活动的 EventSystem,正在 Hot Reload {0} 预制件内启用 EventSystem。点击 Unity UI 上的按钮需要 Unity EventSystem 和输入模块。";
}
}
}
}
#endif
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 0fe4b08bd0d64689be16dc995c89bf1a
timeCreated: 1762538464
AssetOrigin:
serializedVersion: 1
productId: 254358
packageName: Hot Reload | Edit Code Without Compiling
packageVersion: 1.13.17
assetPath: Packages/com.singularitygroup.hotreload/Runtime/Localization/SettingTranslations.cs
uploadId: 870414
@@ -0,0 +1,51 @@
#if ENABLE_MONO && (DEVELOPMENT_BUILD || UNITY_EDITOR)
namespace SingularityGroup.HotReload.Localization {
public static class Locale {
public const string SimplifiedChinese = "zh";
public const string English = "en";
}
internal static partial class Translations {
static string loadedLocale;
static Translations() {
LoadDefaultLocalization();
}
public static void LoadDefaultLocalization() {
LoadLocalization(PackageConst.DefaultLocale);
}
static void LoadLocalization(string locale) {
if (loadedLocale == locale) {
return;
}
if (locale == Locale.SimplifiedChinese) {
LoadSimplifiedChinese();
} else {
LoadEnglish();
}
loadedLocale = locale;
}
public static void LoadEnglish() {
// Load strings from subclasses
Common.LoadEnglish();
Dialogs.LoadEnglish();
Errors.LoadEnglish();
Settings.LoadEnglish();
Logging.LoadEnglish();
Utility.LoadSimplifiedChinese();
}
static void LoadSimplifiedChinese() {
Common.LoadSimplifiedChinese();
Dialogs.LoadSimplifiedChinese();
Errors.LoadSimplifiedChinese();
Settings.LoadSimplifiedChinese();
Logging.LoadSimplifiedChinese();
Utility.LoadSimplifiedChinese();
}
}
}
#endif
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 25df321785e144999ae89a3396247f3d
timeCreated: 1759652512
AssetOrigin:
serializedVersion: 1
productId: 254358
packageName: Hot Reload | Edit Code Without Compiling
packageVersion: 1.13.17
assetPath: Packages/com.singularitygroup.hotreload/Runtime/Localization/Translations.cs
uploadId: 870414
@@ -0,0 +1,45 @@
#if ENABLE_MONO && (DEVELOPMENT_BUILD || UNITY_EDITOR)
namespace SingularityGroup.HotReload.Localization {
internal static partial class Translations {
public static class Utility {
public static string BuildSettings;
public static string IncludeInBuildTooltip;
public static string PlayerSettings;
public static string Other;
public static string FallbackEventSystemTooltip;
public static string NoEventSystemWarning;
public static string OnHotReloadWarning;
public static string MethodCallWarning;
public static string OnHotReloadLocalCallWarning;
public static string OnHotReloadLocalWarning;
public static void LoadEnglish() {
BuildSettings = "Build Settings";
IncludeInBuildTooltip = "Should the Hot Reload runtime be included in development builds? HotReload is never included in release builds.";
PlayerSettings = "Player Settings";
Other = "Other";
FallbackEventSystemTooltip = "Used when project does not create an EventSystem early enough";
NoEventSystemWarning = "No EventSystem is active, enabling an EventSystem inside Hot Reload {0} prefab. A Unity EventSystem and an Input module is required for tapping buttons on the Unity UI.";
OnHotReloadWarning = "failed. Make sure it has 0 parameters, or 1 parameter with type List<MethodPatch>. Exception:";
MethodCallWarning = "failed. Make sure it's a method with 0 parameters either static or defined on MonoBehaviour.";
OnHotReloadLocalCallWarning = "failed. Make sure it has 0 parameters. Exception:";
OnHotReloadLocalWarning = "failed to find method {0}. Make sure it exists within the same class.";
}
public static void LoadSimplifiedChinese() {
BuildSettings = "构建设置";
IncludeInBuildTooltip = "Hot Reload 运行时是否应包含在开发版本中?HotReload 永远不会包含在发布版本中。";
PlayerSettings = "播放器设置";
Other = "其他";
FallbackEventSystemTooltip = "当项目未能及早创建 EventSystem 时使用";
NoEventSystemWarning = "没有活动的 EventSystem,正在 Hot Reload {0} 预制件内启用 EventSystem。点击 Unity UI 上的按钮需要 Unity EventSystem 和输入模块。";
OnHotReloadWarning = "失败。请确保它有 0 个参数,或 1 个类型为 List<MethodPatch> 的参数。异常:";
MethodCallWarning = "失败。请确保它是一个具有 0 个参数的方法,静态或在 MonoBehaviour 上定义。";
OnHotReloadLocalCallWarning = "失败。请确保它有 0 个参数。异常:";
OnHotReloadLocalWarning = "未能找到方法 {0}。请确保它存在于同一个类中。";
}
}
}
}
#endif
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 25823cb7b37e421ca5119f326f3e1b21
timeCreated: 1762675217
AssetOrigin:
serializedVersion: 1
productId: 254358
packageName: Hot Reload | Edit Code Without Compiling
packageVersion: 1.13.17
assetPath: Packages/com.singularitygroup.hotreload/Runtime/Localization/UtilityTranslations.cs
uploadId: 870414
@@ -0,0 +1,114 @@
using System;
using System.Reflection;
using SingularityGroup.HotReload.MonoMod.Utils;
using SingularityGroup.HotReload.Localization;
namespace SingularityGroup.HotReload {
static class MethodCompatiblity {
internal static string CheckCompatibility(MethodBase previousMethod, MethodBase patchMethod) {
var previousConstructor = previousMethod as ConstructorInfo;
var patchConstructor = patchMethod as ConstructorInfo;
if(previousConstructor != null && !ReferenceEquals(patchConstructor, null)) {
return AreConstructorsCompatible(previousConstructor, patchConstructor);
}
var previousMethodInfo = previousMethod as MethodInfo;
var patchMethodInfo = patchMethod as MethodInfo;
if(!ReferenceEquals(previousMethodInfo, null) && !ReferenceEquals(patchMethodInfo, null)) {
return AreMethodInfosCompatible(previousMethodInfo, patchMethodInfo);
}
return Localization.Translations.Logging.UnknownIssue;
}
static string AreMethodBasesCompatible(MethodBase previousMethod, MethodBase patchMethod) {
if(previousMethod.Name != patchMethod.Name) {
return Localization.Translations.Errors.MethodNameMismatch;
}
//Declaring type of patch method is different from the target method but their full name (namespace + name) is equal
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 Localization.Translations.Errors.DeclaringTypeNameMismatch;
}
//Check in case type parameter overloads to distinguish between: void M<T>() { } <-> void M() { }
if(previousMethod.IsGenericMethodDefinition != patchMethod.IsGenericMethodDefinition) {
return Localization.Translations.Errors.IsGenericMethodDefinitionMismatch;
}
var prevParams = previousMethod.GetParameters();
var patchParams = patchMethod.GetParameters();
ArraySegment<ParameterInfo> patchParamsSegment;
bool patchMethodHasExplicitThis;
if(previousMethod.IsStatic || previousMethod.Name.Contains("<") && !patchMethod.IsStatic) {
patchMethodHasExplicitThis = false;
} else {
patchMethodHasExplicitThis = true;
}
if(LikelyHasExplicitThis(prevParams, patchParams, previousMethod)) {
patchMethodHasExplicitThis = true;
}
//Special edge case: User added static keyword to method. No explicit this will be generated in that case
if(!previousMethod.IsStatic && patchMethod.IsStatic && !LikelyHasExplicitThis(prevParams, patchParams, previousMethod)) {
patchMethodHasExplicitThis = false;
}
if(patchMethodHasExplicitThis) {
//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 Localization.Translations.Errors.MissingThisParameter;
}
//this parameter has to be the declaring type
if(!ParamTypeMatches(patchParams[0].ParameterType, previousMethod.DeclaringType)) {
return Localization.Translations.Errors.ThisParameterTypeMismatch;
}
//Ignore the this parameter and compare the remaining ones.
patchParamsSegment = new ArraySegment<ParameterInfo>(patchParams, 1, patchParams.Length - 1);
} else {
patchParamsSegment = new ArraySegment<ParameterInfo>(patchParams);
}
return CompareParameters(new ArraySegment<ParameterInfo>(prevParams), patchParamsSegment);
}
static bool LikelyHasExplicitThis(ParameterInfo[] prevParams, ParameterInfo[] patchParams, MethodBase previousMethod) {
if (patchParams.Length != prevParams.Length + 1) {
return false;
}
var patchT = patchParams[0].ParameterType;
if (!ParamTypeMatches(patchT, previousMethod.DeclaringType)) {
return false;
}
return patchParams[0].Name == "this";
}
static bool ParamTypeMatches(Type patchT, Type originalT) {
return patchT == originalT || patchT.IsByRef && patchT.GetElementType() == originalT;
}
static string CompareParameters(ArraySegment<ParameterInfo> x, ArraySegment<ParameterInfo> y) {
if(x.Count != y.Count) {
return Localization.Translations.Errors.ParameterCountMismatch;
}
for (var i = 0; i < x.Count; i++) {
if(x.Array[i + x.Offset].ParameterType != y.Array[i + y.Offset].ParameterType) {
return Localization.Translations.Errors.ParameterTypeMismatch;
}
}
return null;
}
static string AreConstructorsCompatible(ConstructorInfo x, ConstructorInfo y) {
return AreMethodBasesCompatible(x, y);
}
static string AreMethodInfosCompatible(MethodInfo x, MethodInfo y) {
return AreMethodBasesCompatible(x, y) ?? (x.ReturnType == y.ReturnType ? null : Localization.Translations.Errors.ReturnTypeMismatch);
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: d731e763662b98941bb06ffc6994a9a8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 254358
packageName: Hot Reload | Edit Code Without Compiling
packageVersion: 1.13.17
assetPath: Packages/com.singularitygroup.hotreload/Runtime/MethodCompatiblity.cs
uploadId: 870414
@@ -0,0 +1,713 @@
using System;
using System.Collections.Generic;
using SingularityGroup.HotReload.DTO;
using SingularityGroup.HotReload.Newtonsoft.Json;
namespace SingularityGroup.HotReload.JsonConverters {
internal class MethodPatchResponsesConverter : JsonConverter {
public override bool CanConvert(Type objectType) {
return objectType == typeof(List<MethodPatchResponse>);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) {
var list = new List<MethodPatchResponse>();
while (reader.Read()) {
if (reader.TokenType == JsonToken.StartObject) {
list.Add(ReadMethodPatchResponse(reader));
} else if (reader.TokenType == JsonToken.EndArray) {
break; // End of the SMethod list
}
}
return list;
}
private MethodPatchResponse ReadMethodPatchResponse(JsonReader reader) {
string id = null;
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) {
break;
}
if (reader.TokenType != JsonToken.PropertyName) {
continue;
}
var propertyName = (string)reader.Value;
switch (propertyName) {
case nameof(MethodPatchResponse.id):
id = reader.ReadAsString();
break;
case nameof(MethodPatchResponse.patches):
patches = ReadPatches(reader);
break;
case nameof(MethodPatchResponse.failures):
failures = ReadStringArray(reader);
break;
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
break;
}
}
return new MethodPatchResponse(
id ?? string.Empty,
patches ?? Array.Empty<CodePatch>(),
failures ?? Array.Empty<string>(),
removedMethod ?? Array.Empty<SMethod>(),
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>()
);
}
private CodePatch[] ReadPatches(JsonReader reader) {
var patches = new List<CodePatch>();
while (reader.Read()) {
if (reader.TokenType == JsonToken.EndArray) {
break;
}
if (reader.TokenType != JsonToken.StartObject) {
continue;
}
string patchId = null;
string assemblyName = null;
byte[] patchAssembly = null;
byte[] patchPdb = null;
SMethod[] modifiedMethods = null;
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) {
break;
}
if (reader.TokenType != JsonToken.PropertyName) {
continue;
}
var propertyName = (string)reader.Value;
switch (propertyName) {
case nameof(CodePatch.patchId):
patchId = reader.ReadAsString();
break;
case nameof(CodePatch.assemblyName):
assemblyName = reader.ReadAsString();
break;
case nameof(CodePatch.patchAssembly):
patchAssembly = Convert.FromBase64String(reader.ReadAsString());
break;
case nameof(CodePatch.patchPdb):
patchPdb = Convert.FromBase64String(reader.ReadAsString());
break;
case nameof(CodePatch.modifiedMethods):
modifiedMethods = ReadSMethodArray(reader);
break;
case nameof(CodePatch.patchMethods):
patchMethods = ReadSMethodArray(reader);
break;
case nameof(CodePatch.newMethods):
newMethods = ReadSMethodArray(reader);
break;
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
break;
}
}
patches.Add(new CodePatch(
patchId: patchId ?? string.Empty,
assemblyName: assemblyName ?? string.Empty,
patchAssembly: patchAssembly ?? Array.Empty<byte>(),
patchPdb: patchPdb ?? Array.Empty<byte>(),
modifiedMethods: modifiedMethods ?? Array.Empty<SMethod>(),
patchMethods: patchMethods ?? Array.Empty<SMethod>(),
newMethods: newMethods ?? Array.Empty<SMethod>(),
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>()
));
}
return patches.ToArray();
}
private string[] ReadStringArray(JsonReader reader) {
var list = new List<string>();
while (reader.Read()) {
if (reader.TokenType == JsonToken.String) {
list.Add((string)reader.Value);
} else if (reader.TokenType == JsonToken.EndArray) {
break; // End of the string list
}
}
return list.ToArray();
}
private SMethod[] ReadSMethodArray(JsonReader reader) {
var list = new List<SMethod>();
while (reader.Read()) {
if (reader.TokenType == JsonToken.StartObject) {
list.Add(ReadSMethod(reader));
} else if (reader.TokenType == JsonToken.EndArray) {
break; // End of the SMethod list
}
}
return list.ToArray();
}
private SType[] ReadSTypeArray(JsonReader reader) {
var list = new List<SType>();
while (reader.Read()) {
if (reader.TokenType == JsonToken.StartObject) {
list.Add(ReadSType(reader));
} else if (reader.TokenType == JsonToken.EndArray) {
break; // End of the SType list
}
}
return list.ToArray();
}
private SUnityJob[] ReadSUnityJobArray(JsonReader reader) {
var array = new List<SUnityJob>();
while (reader.Read()) {
if (reader.TokenType == JsonToken.StartObject) {
array.Add(ReadSUnityJob(reader));
} else if (reader.TokenType == JsonToken.EndArray) {
break; // End of the SUnityJob array
}
}
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);
string simpleName = 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(SMethod.assemblyName):
assemblyName = reader.ReadAsString();
break;
case nameof(SMethod.displayName):
displayName = reader.ReadAsString();
break;
case nameof(SMethod.metadataToken):
metadataToken = reader.ReadAsInt32() ?? default(int);
break;
case nameof(SMethod.simpleName):
simpleName = reader.ReadAsString();
break;
default:
reader.Skip(); // Skip unknown properties
break;
}
}
return new SMethod(
assemblyName ?? string.Empty,
displayName ?? string.Empty,
metadataToken,
simpleName ?? string.Empty
);
}
private SType ReadSType(JsonReader reader) {
string assemblyName = null;
string typeName = null;
int? metadataToken = null;
while (reader.Read()) {
if (reader.TokenType == JsonToken.Null) {
return null;
}
if (reader.TokenType == JsonToken.EndObject) {
break;
}
if (reader.TokenType != JsonToken.PropertyName) {
continue;
}
var propertyName = (string)reader.Value;
switch (propertyName) {
case nameof(SType.assemblyName):
assemblyName = reader.ReadAsString();
break;
case nameof(SType.typeName):
typeName = reader.ReadAsString();
break;
case nameof(SType.metadataToken):
metadataToken = reader.ReadAsInt32();
break;
default:
reader.Skip(); // Skip unknown properties
break;
}
}
return new SType(
assemblyName ?? string.Empty,
typeName ?? string.Empty,
metadataToken ?? 0
);
}
private SUnityJob ReadSUnityJob(JsonReader reader) {
int metadataToken = default(int);
UnityJobKind jobKind = default(UnityJobKind);
while (reader.Read()) {
if (reader.TokenType == JsonToken.EndObject) {
break;
}
if (reader.TokenType != JsonToken.PropertyName) {
continue;
}
var propertyName = (string)reader.Value;
switch (propertyName) {
case nameof(SUnityJob.metadataToken):
metadataToken = reader.ReadAsInt32() ?? 0;
break;
case nameof(SUnityJob.jobKind):
var jobKindStr = reader.ReadAsString();
Enum.TryParse(jobKindStr, out jobKind);
break;
default:
reader.Skip(); // Skip unknown properties
break;
}
}
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;
if (responses == null) {
writer.WriteNull();
return;
}
writer.WriteStartArray();
foreach (var response in responses) {
writer.WriteStartObject();
writer.WritePropertyName(nameof(response.id));
writer.WriteValue(response.id);
if (response.patches != null) {
writer.WritePropertyName(nameof(response.patches));
writer.WriteStartArray();
foreach (var responsePatch in response.patches) {
writer.WriteStartObject();
writer.WritePropertyName(nameof(responsePatch.patchId));
writer.WriteValue(responsePatch.patchId);
writer.WritePropertyName(nameof(responsePatch.assemblyName));
writer.WriteValue(responsePatch.assemblyName);
writer.WritePropertyName(nameof(responsePatch.patchAssembly));
writer.WriteValue(Convert.ToBase64String(responsePatch.patchAssembly));
writer.WritePropertyName(nameof(responsePatch.patchPdb));
writer.WriteValue(Convert.ToBase64String(responsePatch.patchPdb));
if (responsePatch.modifiedMethods != null) {
writer.WritePropertyName(nameof(responsePatch.modifiedMethods));
writer.WriteStartArray();
foreach (var modifiedMethod in responsePatch.modifiedMethods) {
WriteSMethod(writer, modifiedMethod);
}
writer.WriteEndArray();
}
if (responsePatch.patchMethods != null) {
writer.WritePropertyName(nameof(responsePatch.patchMethods));
writer.WriteStartArray();
foreach (var patchMethod in responsePatch.patchMethods) {
WriteSMethod(writer, patchMethod);
}
writer.WriteEndArray();
}
if (responsePatch.newMethods != null) {
writer.WritePropertyName(nameof(responsePatch.newMethods));
writer.WriteStartArray();
foreach (var newMethod in responsePatch.newMethods) {
WriteSMethod(writer, newMethod);
}
writer.WriteEndArray();
}
if (responsePatch.unityJobs != null) {
writer.WritePropertyName(nameof(responsePatch.unityJobs));
writer.WriteStartArray();
foreach (var unityJob in responsePatch.unityJobs) {
writer.WriteStartObject();
writer.WritePropertyName(nameof(unityJob.metadataToken));
writer.WriteValue(unityJob.metadataToken);
writer.WritePropertyName(nameof(unityJob.jobKind));
writer.WriteValue(unityJob.jobKind.ToString());
writer.WriteEndObject();
}
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();
}
writer.WriteEndArray();
}
if (response.failures != null) {
writer.WritePropertyName(nameof(response.failures));
writer.WriteStartArray();
foreach (var failure in response.failures) {
writer.WriteValue(failure);
}
writer.WriteEndArray();
}
if (response.removedMethod != null) {
writer.WritePropertyName(nameof(response.removedMethod));
writer.WriteStartArray();
foreach (var removedMethod in response.removedMethod) {
WriteSMethod(writer, removedMethod);
}
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();
}
void WriteSMethod(JsonWriter writer, SMethod method) {
writer.WriteStartObject();
writer.WritePropertyName(nameof(method.assemblyName));
writer.WriteValue(method.assemblyName);
writer.WritePropertyName(nameof(method.displayName));
writer.WriteValue(method.displayName);
writer.WritePropertyName(nameof(method.metadataToken));
writer.WriteValue(method.metadataToken);
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();
}
}
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);
writer.WritePropertyName(nameof(type.metadataToken));
writer.WriteValue(type.metadataToken);
writer.WriteEndObject();
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: a35195732a094716a76d7125122c90fe
timeCreated: 1685732397
AssetOrigin:
serializedVersion: 1
productId: 254358
packageName: Hot Reload | Edit Code Without Compiling
packageVersion: 1.13.17
assetPath: Packages/com.singularitygroup.hotreload/Runtime/MethodPatchResponsesConverter.cs
uploadId: 870414
@@ -0,0 +1,33 @@
using System;
using System.Reflection;
namespace SingularityGroup.HotReload {
static class MethodUtils {
#if ENABLE_MONO
public static unsafe void DisableVisibilityChecks(MethodBase method) {
if(IntPtr.Size == sizeof(long)) {
var ptr = (Interop.MonoMethod64*)method.MethodHandle.Value.ToPointer();
ptr->monoMethodFlags |= Interop.MonoMethodFlags.skip_visibility;
} else {
var ptr = (Interop.MonoMethod32*)method.MethodHandle.Value.ToPointer();
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
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: c4f19b17adc17a94192a325012f153db
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 254358
packageName: Hot Reload | Edit Code Without Compiling
packageVersion: 1.13.17
assetPath: Packages/com.singularitygroup.hotreload/Runtime/MethodUtils.cs
uploadId: 870414
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: a2fd781ae18045f0b7690cd490737996
timeCreated: 1675064423
@@ -0,0 +1,82 @@
#if ENABLE_MONO && (DEVELOPMENT_BUILD || UNITY_EDITOR)
using SingularityGroup.HotReload.Localization;
using UnityEngine;
using UnityEngine.UI;
namespace SingularityGroup.HotReload {
internal class ConnectionDialog : MonoBehaviour {
[Header(Localization.Translations.MenuItems.UIControls)]
public Button buttonHide;
[Header(Localization.Translations.MenuItems.Information)]
public Text textSummary;
public Text textSuggestion;
void Start() {
buttonHide.onClick.AddListener(Hide);
}
public int pendingPatches = 0;
public int patchesApplied = 0;
private void Awake() {
SyncPatchCounts();
}
bool SyncPatchCounts() {
var changed = false;
if (pendingPatches != CodePatcher.I.PendingPatches.Count) {
pendingPatches = CodePatcher.I.PendingPatches.Count;
changed = true;
}
if (patchesApplied != CodePatcher.I.PatchesApplied) {
patchesApplied = CodePatcher.I.PatchesApplied;
changed = true;
}
return changed;
}
/// <param name="summary">One of the <see cref="ConnectionSummary"/> constants</param>
public void SetSummary(string summary) {
if (textSummary != null) textSummary.text = summary;
isConnected = summary == ConnectionSummary.Connected;
}
private bool isConnected = false;
// assumes that auto-pair already tried for several seconds
void Update() {
textSuggestion.enabled = isConnected;
if (SyncPatchCounts()) {
textSuggestion.text = string.Format(Localization.Translations.Dialogs.PatchesStatus, pendingPatches, patchesApplied);
}
}
/// hide this dialog
void Hide() {
gameObject.SetActive(false); // this should disable the Update loop?
}
}
/// <summary>
/// The connection between device and Hot Reload can be summarized in a few words.
/// </summary>
/// <remarks>
/// The summary may be shown for less than a second, as the connection can change without warning.<br/>
/// Therefore, we use short and simple messages.
/// </remarks>
internal static class ConnectionSummary {
public static string Cancelled => Localization.Translations.Dialogs.ConnectionStateCancelled;
public static string Connecting => Localization.Translations.Dialogs.ConnectionStateConnecting;
public static string Handshaking => Localization.Translations.Dialogs.ConnectionStateHandshaking;
public static string DifferencesFound => Localization.Translations.Dialogs.ConnectionStateDifferencesFound;
public static string Connected => Localization.Translations.Dialogs.ConnectionStateConnected;
// reconnecting can be shown for a long time, so a longer message is okay
public static string TryingToReconnect => Localization.Translations.Dialogs.TryingToReconnect;
public static string Disconnected => Localization.Translations.Dialogs.Disconnected;
}
}
#endif
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: bb1cc47c374f478e861f2c3dade07e1a
timeCreated: 1675064498
AssetOrigin:
serializedVersion: 1
productId: 254358
packageName: Hot Reload | Edit Code Without Compiling
packageVersion: 1.13.17
assetPath: Packages/com.singularitygroup.hotreload/Runtime/MonoBehaviours/ConnectionDialog.cs
uploadId: 870414
@@ -0,0 +1,144 @@
#if ENABLE_MONO && (DEVELOPMENT_BUILD || UNITY_EDITOR)
using System;
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 {
public GameObject retryPrompt;
public GameObject connectedPrompt;
public GameObject questionPrompt;
[Header(Localization.Translations.MenuItems.Other)]
[Tooltip(Localization.Translations.MenuItems.FalllbackEventSystem)]
public GameObject fallbackEventSystem;
#region Singleton
private static Prompts _I;
/// <summary>
/// All usages must check that <see cref="PlayerEntrypoint.RuntimeSupportsHotReload"/> is true before accessing this singleton.
/// </summary>
/// <remarks>
/// This getter can throw on unsupported platforms (HotReloadSettingsObject resource doesn't exist on unsupported platforms).
/// </remarks>
public static Prompts I {
get {
if (_I == null) {
// allow showing prompts in editor (for testing)
if (!Application.isEditor && !PlayerEntrypoint.IsPlayerWithHotReload()) {
throw new NotSupportedException(Localization.Translations.Errors.IsPlayerWithHotReloadFalse);
}
var go = Instantiate(HotReloadSettingsObject.I.PromptsPrefab,
new Vector3(0, 0, 0), Quaternion.identity);
go.name = nameof(Prompts) + "_singleton";
if (Application.isPlaying) {
DontDestroyOnLoad(go);
}
_I = go.GetComponentInChildren<Prompts>();
}
return _I;
}
}
#endregion
/// <seealso cref="ShowConnectionDialog"/>
public static void SetConnectionState(string state, bool log = true) {
var connectionDialog = I.connectedPrompt.GetComponentInChildren<ConnectionDialog>();
if (log) Log.Debug($"SetConnectionState( {state} )");
if (connectionDialog) {
connectionDialog.SetSummary(state);
}
}
/// <seealso cref="SetConnectionState"/>
public static void ShowConnectionDialog() {
I.retryPrompt.SetActive(false);
I.connectedPrompt.SetActive(true);
}
public static async Task<bool> ShowQuestionDialog(QuestionDialog.Config config) {
var tcs = new TaskCompletionSource<bool>();
var holder = I.questionPrompt;
var dialog = holder.GetComponentInChildren<QuestionDialog>();
dialog.completion = tcs;
dialog.UpdateView(config);
holder.SetActive(true);
return await tcs.Task;
}
public static void ShowRetryDialog(
PatchServerInfo patchServerInfo,
ServerHandshake.Result handshakeResults = ServerHandshake.Result.None,
bool auto = true
) {
var retryDialog = I.retryPrompt.GetComponentInChildren<RetryDialog>();
RetryDialog.TargetServer = patchServerInfo;
RetryDialog.HandshakeResults = handshakeResults;
if (patchServerInfo == null) {
retryDialog.DebugInfo = $"patchServerInfo == null {handshakeResults}";
} else {
retryDialog.DebugInfo = $"{RequestHelper.CreateUrl(patchServerInfo)} {handshakeResults}";
}
retryDialog.autoConnect = auto;
I.connectedPrompt.SetActive(false);
I.retryPrompt.SetActive(true);
}
#region fallback event system
private void Start() {
StartCoroutine(DelayedEnsureEventSystem());
}
private bool userTriedToInteract = false;
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
}
}
private IEnumerator DelayedEnsureEventSystem() {
// allow some delay in-case the project loads the EventSystem asynchronously (perhaps in a second scene)
if (EventSystem.current == null) {
yield return new WaitForSeconds(1f);
DoEnsureEventSystem();
}
}
/// Scene must contain an EventSystem and StandaloneInputModule, otherwise clicking/tapping on the overlay does nothing.
private void DoEnsureEventSystem() {
if (EventSystem.current == null) {
Log.Info(string.Format(Localization.Translations.Settings.NoEventSystemWarning, name));
fallbackEventSystem.SetActive(true);
}
}
#endregion
}
}
#endif
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: d92cdbfacafd433ca77184c22a384a6d
timeCreated: 1674488132
AssetOrigin:
serializedVersion: 1
productId: 254358
packageName: Hot Reload | Edit Code Without Compiling
packageVersion: 1.13.17
assetPath: Packages/com.singularitygroup.hotreload/Runtime/MonoBehaviours/Prompts.cs
uploadId: 870414
@@ -0,0 +1,67 @@
#if ENABLE_MONO && (DEVELOPMENT_BUILD || UNITY_EDITOR)
using System;
using System.Threading.Tasks;
using SingularityGroup.HotReload.Localization;
using UnityEngine;
using UnityEngine.UI;
namespace SingularityGroup.HotReload {
class QuestionDialog : MonoBehaviour {
[Header(Localization.Translations.MenuItems.Information)]
public Text textSummary;
public Text textSuggestion;
[Header(Localization.Translations.MenuItems.UIControls)]
public Button buttonContinue;
public Button buttonCancel;
public Button buttonMoreInfo;
public TaskCompletionSource<bool> completion = new TaskCompletionSource<bool>();
public void UpdateView(Config config) {
textSummary.text = config.summary;
textSuggestion.text = config.suggestion;
if (string.IsNullOrEmpty(config.continueButtonText)) {
buttonContinue.enabled = false;
} else {
buttonContinue.GetComponentInChildren<Text>().text = config.continueButtonText;
buttonContinue.onClick.AddListener(() => {
completion.TrySetResult(true);
Hide();
});
}
if (string.IsNullOrEmpty(config.cancelButtonText)) {
buttonCancel.enabled = false;
} else {
buttonCancel.GetComponentInChildren<Text>().text = config.cancelButtonText;
buttonCancel.onClick.AddListener(() => {
completion.TrySetResult(false);
Hide();
});
}
buttonMoreInfo.onClick.AddListener(() => {
Application.OpenURL(config.moreInfoUrl);
});
}
internal class Config {
public string summary;
public string suggestion;
public string continueButtonText = Localization.Translations.Dialogs.ContinueButtonText;
public string cancelButtonText = Localization.Translations.Dialogs.CancelButtonText;
public string moreInfoUrl = PackageConst.DefaultLocale == Locale.SimplifiedChinese ?
"https://hotreload.net/zh/documentation/on-device#处理不同的提交" :
"https://hotreload.net/documentation/on-device#handling-different-commits";
}
/// hide this dialog
void Hide() {
gameObject.SetActive(false); // this should disable the Update loop?
}
}
}
#endif
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: ef31038a0ed84685b779466bf22d53a9
timeCreated: 1675143382
AssetOrigin:
serializedVersion: 1
productId: 254358
packageName: Hot Reload | Edit Code Without Compiling
packageVersion: 1.13.17
assetPath: Packages/com.singularitygroup.hotreload/Runtime/MonoBehaviours/QuestionDialog.cs
uploadId: 870414
@@ -0,0 +1,108 @@
#if ENABLE_MONO && (DEVELOPMENT_BUILD || UNITY_EDITOR)
using JetBrains.Annotations;
using SingularityGroup.HotReload.Localization;
using UnityEngine;
using UnityEngine.UI;
namespace SingularityGroup.HotReload {
internal class RetryDialog : MonoBehaviour {
[Header(Localization.Translations.MenuItems.UIControls)]
public Button buttonHide;
public Button buttonRetryAutoPair;
public Button buttonTroubleshoot;
public Text textSummary;
public Text textSuggestion;
public InputField ipInput;
[Tooltip("Hidden by default")]
public Text textForDebugging;
[Header("For HotReload Devs")]
// In Unity Editor, click checkbox to see info helpful for debugging bugs
public bool enableDebugging;
// [Header("Other")]
// [Tooltip("Used when your project does not create an EventSystem early enough")]
// public GameObject fallbackEventSystem;
private static RetryDialog _I;
public string DebugInfo {
set {
textForDebugging.text = value;
}
}
public bool autoConnect { get; set; }
void Start() {
buttonHide.onClick.AddListener(() => {
Hide();
});
buttonRetryAutoPair.onClick.AddListener(() => {
Hide();
int port;
var ipAndPort = ipInput.textComponent.text.Split(':');
if (ipAndPort.Length != 2 || !int.TryParse(ipAndPort[1], out port)) {
port = PlayerEntrypoint.PlayerBuildInfo?.buildMachinePort ?? RequestHelper.defaultPort;
}
var ip = ipAndPort.Length > 0 ? ipAndPort[0] : string.Empty;
PlayerEntrypoint.TryConnectToIpAndPort(ip, port);
});
buttonTroubleshoot.onClick.AddListener(() => {
var docsUrl = PackageConst.DefaultLocale == Locale.SimplifiedChinese ?
"https://hotreload.net/zh/documentation/on-device#连接问题" :
"https://hotreload.net/documentation/on-device#connection-issues" ;
Application.OpenURL(docsUrl);
});
}
[CanBeNull]
public static PatchServerInfo TargetServer { private get; set; } = null;
public static ServerHandshake.Result HandshakeResults { private get; set; } = ServerHandshake.Result.None;
private void OnEnable() {
ipInput.text = $"{PlayerEntrypoint.PlayerBuildInfo?.buildMachineHostName}:{PlayerEntrypoint.PlayerBuildInfo?.buildMachinePort}";
UpdateUI();
}
void Update() {
UpdateUI();
}
void UpdateUI() {
// assumes that auto-pair already tried for several seconds
// suggestions to help the user when auto-pair is failing
var networkText = Application.isMobilePlatform ? "WiFi" : "LAN/WiFi";
var noWifiNetwork = string.Format(Localization.Translations.Dialogs.IsConnected, networkText);
var waitForCompiling = Localization.Translations.Dialogs.WaitForCompiling;
var targetNetworkIsReachable = string.Format(Localization.Translations.Dialogs.TargetNetworkIsReachable, networkText);
if (Application.internetReachability != NetworkReachability.ReachableViaLocalAreaNetwork) {
textSuggestion.text = noWifiNetwork;
} else if (HandshakeResults.HasFlag(ServerHandshake.Result.WaitForCompiling)) {
// Note: Technically the player could do the waiting itself, and handshake again with the server
// only after compiling finishes... Telling the user to do that is easier to implement though.
textSuggestion.text = waitForCompiling;
} else {
textSuggestion.text = targetNetworkIsReachable;
}
textSummary.text = autoConnect ? Localization.Translations.Dialogs.AutoPairEncounteredIssue : Localization.Translations.Dialogs.ConnectionFailed;
if (enableDebugging && textForDebugging) {
textForDebugging.enabled = true;
textForDebugging.text = $"the target = {TargetServer}";
}
}
/// hide this dialog
void Hide() {
gameObject.SetActive(false); // this should disable the Update loop?
}
}
}
#endif
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 7a69f8e8e50a405a84ec22ac7c2f4bdc
timeCreated: 1674408078
AssetOrigin:
serializedVersion: 1
productId: 254358
packageName: Hot Reload | Edit Code Without Compiling
packageVersion: 1.13.17
assetPath: Packages/com.singularitygroup.hotreload/Runtime/MonoBehaviours/RetryDialog.cs
uploadId: 870414
@@ -0,0 +1,194 @@
using System;
using System.Runtime.InteropServices;
namespace SingularityGroup.HotReload.Interop {
//see _MonoMethod struct in class-internals.h
[StructLayout(LayoutKind.Explicit, CharSet = CharSet.Auto, Size = 8 + sizeof(long) * 3 + 4)]
internal unsafe struct MonoMethod64 {
[FieldOffset(0)]
public MethodAttributes flags;
[FieldOffset(2)]
public MethodImplAttributes iflags;
[FieldOffset(4)]
public uint token;
[FieldOffset(8)]
public void* klass;
[FieldOffset(8 + sizeof(long))]
public void* signature;
[FieldOffset(8 + sizeof(long) * 2)]
public char* name;
/* this is used by the inlining algorithm */
[FieldOffset(8 + sizeof(long) * 3)]
public MonoMethodFlags monoMethodFlags;
[FieldOffset(8 + sizeof(long) * 3 + 2)]
public short slot;
}
[StructLayout(LayoutKind.Explicit, CharSet = CharSet.Auto, Size = 8 + sizeof(int) * 3 + 4)]
internal unsafe struct MonoMethod32 {
[FieldOffset(0)]
public MethodAttributes flags;
[FieldOffset(2)]
public MethodImplAttributes iflags;
[FieldOffset(4)]
public uint token;
[FieldOffset(8)]
public void* klass;
[FieldOffset(8 + sizeof(int))]
public void* signature;
[FieldOffset(8 + sizeof(int) * 2)]
public char* name;
/* this is used by the inlining algorithm */
[FieldOffset(8 + sizeof(int) * 3)]
public MonoMethodFlags monoMethodFlags;
[FieldOffset(8 + sizeof(int) * 3 + 2)]
public short slot;
}
//Corresponds to the bitflags of the _MonoMethod struct
[Flags]
internal enum MonoMethodFlags : ushort {
inline_info = 1 << 0, //:1
inline_failure = 1 << 1, //:1
wrapper_type = 1 << 2, //:5
string_ctor = 1 << 7, //:1
save_lmf = 1 << 8, //:1
dynamic = 1 << 9, //:1 /* created & destroyed during runtime */
sre_method = 1 << 10, //:1 /* created at runtime using Reflection.Emit */
is_generic = 1 << 11, //:1 /* whenever this is a generic method definition */
is_inflated = 1 << 12, //:1 /* whether we're a MonoMethodInflated */
skip_visibility = 1 << 13, //:1 /* whenever to skip JIT visibility checks */
verification_success = 1 << 14, //:1 /* whether this method has been verified successfully.*/
}
[Flags]
internal enum MethodImplAttributes : ushort {
/// <summary><para>Specifies that the method implementation is in Microsoft intermediate language (MSIL).</para></summary>
IL = 0,
/// <summary><para>Specifies that the method is implemented in managed code. </para></summary>
Managed = 0,
/// <summary><para>Specifies that the method implementation is native.</para></summary>
Native = 1,
/// <summary><para>Specifies that the method implementation is in Optimized Intermediate Language (OPTIL).</para></summary>
OPTIL = 2,
/// <summary><para>Specifies flags about code type.</para></summary>
CodeTypeMask = 3,
/// <summary><para>Specifies that the method implementation is provided by the runtime.</para></summary>
Runtime = 3,
/// <summary><para>Specifies whether the method is implemented in managed or unmanaged code.</para></summary>
ManagedMask = 4,
/// <summary><para>Specifies that the method is implemented in unmanaged code.</para></summary>
Unmanaged = 4,
/// <summary><para>Specifies that the method cannot be inlined.</para></summary>
NoInlining = 8,
/// <summary><para>Specifies that the method is not defined.</para></summary>
ForwardRef = 16, // 0x00000010
/// <summary><para>Specifies that the method is single-threaded through the body. Static methods (Shared in Visual Basic) lock on the type, whereas instance methods lock on the instance. You can also use the C# <format type="text/html"><a href="656DA1A4-707E-4EF6-9C6E-6D13B646AF42">lock statement</a></format> or the Visual Basic <format type="text/html"><a href="14501703-298f-4d43-b139-c4b6366af176">SyncLock statement</a></format> for this purpose. </para></summary>
Synchronized = 32, // 0x00000020
/// <summary><para>Specifies that the method is not optimized by the just-in-time (JIT) compiler or by native code generation (see <format type="text/html"><a href="44bf97aa-a9a4-4eba-9a0d-cfaa6fc53a66">Ngen.exe</a></format>) when debugging possible code generation problems.</para></summary>
NoOptimization = 64, // 0x00000040
/// <summary><para>Specifies that the method signature is exported exactly as declared.</para></summary>
PreserveSig = 128, // 0x00000080
/// <summary><para>Specifies that the method should be inlined wherever possible.</para></summary>
AggressiveInlining = 256, // 0x00000100
/// <summary><para>Specifies an internal call.</para></summary>
InternalCall = 4096, // 0x00001000
/// <summary><para>Specifies a range check value.</para></summary>
MaxMethodImplVal = 65535, // 0x0000FFFF
}
/// <summary><para>Specifies flags for method attributes. These flags are defined in the corhdr.h file.</para></summary>
[Flags]
internal enum MethodAttributes : ushort {
/// <summary><para>Retrieves accessibility information.</para></summary>
MemberAccessMask = 7,
/// <summary><para>Indicates that the member cannot be referenced.</para></summary>
PrivateScope = 0,
/// <summary><para>Indicates that the method is accessible only to the current class.</para></summary>
Private = 1,
/// <summary><para>Indicates that the method is accessible to members of this type and its derived types that are in this assembly only.</para></summary>
FamANDAssem = 2,
/// <summary><para>Indicates that the method is accessible to any class of this assembly.</para></summary>
Assembly = FamANDAssem | Private, // 0x00000003
/// <summary><para>Indicates that the method is accessible only to members of this class and its derived classes.</para></summary>
Family = 4,
/// <summary><para>Indicates that the method is accessible to derived classes anywhere, as well as to any class in the assembly.</para></summary>
FamORAssem = Family | Private, // 0x00000005
/// <summary><para>Indicates that the method is accessible to any object for which this object is in scope.</para></summary>
Public = Family | FamANDAssem, // 0x00000006
/// <summary><para>Indicates that the method is defined on the type; otherwise, it is defined per instance.</para></summary>
Static = 16, // 0x00000010
/// <summary><para>Indicates that the method cannot be overridden.</para></summary>
Final = 32, // 0x00000020
/// <summary><para>Indicates that the method is virtual.</para></summary>
Virtual = 64, // 0x00000040
/// <summary><para>Indicates that the method hides by name and signature; otherwise, by name only.</para></summary>
HideBySig = 128, // 0x00000080
/// <summary><para>Indicates that the method can only be overridden when it is also accessible.</para></summary>
CheckAccessOnOverride = 512, // 0x00000200
/// <summary><para>Retrieves vtable attributes.</para></summary>
VtableLayoutMask = 256, // 0x00000100
/// <summary><para>Indicates that the method will reuse an existing slot in the vtable. This is the default behavior.</para></summary>
ReuseSlot = 0,
/// <summary><para>Indicates that the method always gets a new slot in the vtable.</para></summary>
NewSlot = VtableLayoutMask, // 0x00000100
/// <summary><para>Indicates that the class does not provide an implementation of this method.</para></summary>
Abstract = 1024, // 0x00000400
/// <summary><para>Indicates that the method is special. The name describes how this method is special.</para></summary>
SpecialName = 2048, // 0x00000800
/// <summary><para>Indicates that the method implementation is forwarded through PInvoke (Platform Invocation Services).</para></summary>
PinvokeImpl = 8192, // 0x00002000
/// <summary><para>Indicates that the managed method is exported by thunk to unmanaged code.</para></summary>
UnmanagedExport = 8,
/// <summary><para>Indicates that the common language runtime checks the name encoding.</para></summary>
RTSpecialName = 4096, // 0x00001000
/// <summary><para>Indicates a reserved flag for runtime use only.</para></summary>
ReservedMask = 53248, // 0x0000D000
/// <summary><para>Indicates that the method has security associated with it. Reserved flag for runtime use only.</para></summary>
HasSecurity = 16384, // 0x00004000
/// <summary><para>Indicates that the method calls another method containing security code. Reserved flag for runtime use only.</para></summary>
RequireSecObject = 32768, // 0x00008000
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: bb9456a28c3b8644e9b0f78eb6d9ac17
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 254358
packageName: Hot Reload | Edit Code Without Compiling
packageVersion: 1.13.17
assetPath: Packages/com.singularitygroup.hotreload/Runtime/MonoMethod.cs
uploadId: 870414
@@ -0,0 +1,31 @@
using System;
using System.IO;
namespace SingularityGroup.HotReload {
// Largely copied from CommandLineParameters.ReadIsClone of UnityEditor.MultiplayerModule.dll
internal static class MultiplayerPlaymodeHelper {
#if UNITY_EDITOR && UNITY_2023_1_OR_NEWER
public static bool? isClone;
public static bool IsClone => isClone == null ? (isClone = HasCommandLineArgument(Environment.GetCommandLineArgs(), "--virtual-project-clone")).Value : isClone.Value;
#else
public static bool IsClone => false;
#endif
public static bool HasCommandLineArgument(string[] commandLineArgs, string argumentName) {
foreach (string commandLineArg in commandLineArgs) {
if (commandLineArg == argumentName) {
return true;
}
}
return false;
}
public static string PathToMainProject(string path) {
if (IsClone) {
// Library/VP/<clone-id> is the base path
return Path.Combine("..", "..", "..", path);
}
return path;
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: caf15b822bf74e8491daf33e58c4f11e
timeCreated: 1768677740
AssetOrigin:
serializedVersion: 1
productId: 254358
packageName: Hot Reload | Edit Code Without Compiling
packageVersion: 1.13.17
assetPath: Packages/com.singularitygroup.hotreload/Runtime/MultiplayerPlaymodeHelper.cs
uploadId: 870414
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 62fd71232a4784cdeb53a6ab67694087
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,87 @@
fileFormatVersion: 2
guid: 56a90f73ab41d4145bb173186644f641
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 1
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
: Any
second:
enabled: 0
settings:
Exclude Android: 1
Exclude Editor: 0
Exclude Linux64: 1
Exclude OSXUniversal: 0
Exclude Win: 1
Exclude Win64: 1
Exclude iOS: 1
- first:
Android: Android
second:
enabled: 0
settings:
CPU: ARMv7
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 1
settings:
CPU: AnyCPU
DefaultValueInitialized: true
OS: OSX
- first:
Standalone: Linux64
second:
enabled: 0
settings:
CPU: AnyCPU
- first:
Standalone: OSXUniversal
second:
enabled: 1
settings:
CPU: AnyCPU
- first:
Standalone: Win
second:
enabled: 0
settings:
CPU: x86
- first:
Standalone: Win64
second:
enabled: 0
settings:
CPU: x86_64
- first:
iPhone: iOS
second:
enabled: 0
settings:
AddToEmbeddedBinaries: false
CPU: AnyCPU
CompileFlags:
FrameworkDependencies:
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 254358
packageName: Hot Reload | Edit Code Without Compiling
packageVersion: 1.13.17
assetPath: Packages/com.singularitygroup.hotreload/Runtime/OSX/HotReloadNativeHelper.dylib
uploadId: 870414
@@ -0,0 +1,193 @@
#pragma warning disable CS0618 // obsolete warnings (stay warning-free also in newer unity versions)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using UnityEngine;
using Object = UnityEngine.Object;
using SingularityGroup.HotReload.Localization;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace SingularityGroup.HotReload {
static class Dispatch {
// DispatchOnHotReload is called every time a patch is applied (1x per batch of filechanges)
public static async Task OnHotReload(List<MethodPatch> patchedMethods) {
var methods = await Task.Run(() => GetOrFillMethodsCacheThreaded());
foreach (var m in methods) {
if (m.IsStatic) {
InvokeStaticMethod(m, nameof(InvokeOnHotReload), patchedMethods);
} else {
foreach (var go in GameObject.FindObjectsOfType(m.DeclaringType)) {
InvokeInstanceMethod(m, go, patchedMethods);
}
}
}
}
public static void OnHotReloadLocal(MethodBase originalMethod, MethodBase patchMethod) {
if (!Attribute.IsDefined(originalMethod, typeof(InvokeOnHotReloadLocal))) {
return;
}
var attrib = Attribute.GetCustomAttribute(originalMethod, typeof(InvokeOnHotReloadLocal)) as InvokeOnHotReloadLocal;
if (!string.IsNullOrEmpty(attrib?.methodToInvoke)) {
OnHotReloadLocalCustom(originalMethod, attrib);
return;
}
var patchMethodParams = patchMethod.GetParameters();
if (patchMethodParams.Length == 0) {
InvokeStaticMethod(patchMethod, nameof(InvokeOnHotReloadLocal), null);
} else if (typeof(MonoBehaviour).IsAssignableFrom(patchMethodParams[0].ParameterType)) {
foreach (var go in GameObject.FindObjectsOfType(patchMethodParams[0].ParameterType)) {
InvokeInstanceMethodStatic(patchMethod, go);
}
} else {
Log.Warning($"[{nameof(InvokeOnHotReloadLocal)}] {patchMethod.DeclaringType?.Name} {patchMethod.Name} {Localization.Translations.Utility.MethodCallWarning}");
}
}
public static void OnHotReloadLocalCustom(MethodBase origianlMethod, InvokeOnHotReloadLocal attrib) {
var reloadForType = origianlMethod.DeclaringType;
var reloadMethod = reloadForType?.GetMethod(attrib.methodToInvoke, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
if (reloadMethod == null) {
Log.Warning($"[{nameof(InvokeOnHotReloadLocal)}] {string.Format(Localization.Translations.Utility.OnHotReloadLocalWarning, attrib.methodToInvoke)}");
return;
}
if (reloadMethod.IsStatic) {
InvokeStaticMethod(reloadMethod, nameof(InvokeOnHotReloadLocal), null);
} else if (typeof(MonoBehaviour).IsAssignableFrom(reloadForType)) {
foreach (var go in GameObject.FindObjectsOfType(reloadForType)) {
InvokeInstanceMethod(reloadMethod, go, null);
}
} else {
Log.Warning($"[{nameof(InvokeOnHotReloadLocal)}] {reloadMethod.DeclaringType?.Name} {reloadMethod.Name} {Localization.Translations.Utility.MethodCallWarning}");
}
}
private static List<MethodInfo> methodsCache;
private static List<MethodInfo> GetOrFillMethodsCacheThreaded() {
if (methodsCache != null) {
return methodsCache;
}
#if UNITY_2019_1_OR_NEWER && UNITY_EDITOR
var methodCollection = UnityEditor.TypeCache.GetMethodsWithAttribute(typeof(InvokeOnHotReload));
var methods = new List<MethodInfo>();
foreach (var m in methodCollection) {
methods.Add(m);
}
#else
var methods = GetMethodsReflection();
#endif
methodsCache = methods;
return methods;
}
private static List<MethodInfo> GetMethodsReflection() {
var methods = new List<MethodInfo>();
try {
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies()) {
if (asm.FullName == "System" || asm.FullName.StartsWith("System.", StringComparison.Ordinal)) {
continue; // big performance optimization
}
try {
foreach (var type in asm.GetTypes()) {
try {
foreach (var m in type.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)) {
try {
if (Attribute.IsDefined(m, typeof(InvokeOnHotReload))) {
methods.Add(m);
}
} catch (BadImageFormatException) {
// silently ignore (can happen, is very annoying if it spams)
/*
BadImageFormatException: VAR 3 (TOutput) cannot be expanded in this context with 3 instantiations
System.Reflection.MonoMethod.GetBaseMethod () (at <c8d0d7b9135640958bff528a1e374758>:0)
System.MonoCustomAttrs.GetBase (System.Reflection.ICustomAttributeProvider obj) (at <c8d0d7b9135640958bff528a1e374758>:0)
System.MonoCustomAttrs.IsDefined (System.Reflection.ICustomAttributeProvider obj, System.Type attributeType, System.Boolean inherit) (at <c8d0d7b9135640958bff528a1e374758>:0)
*/
} catch (TypeLoadException) {
// silently ignore (can happen, is very annoying if it spams)
} catch (Exception e) {
ThreadUtility.LogException(new AggregateException(type.Name + "." + m.Name, e));
}
}
} catch (BadImageFormatException) {
// silently ignore (can happen, is very annoying if it spams)
} catch (TypeLoadException) {
// silently ignore (can happen, is very annoying if it spams)
} catch (Exception e) {
ThreadUtility.LogException(new AggregateException(type.Name, e));
}
}
} catch (BadImageFormatException) {
// silently ignore (can happen, is very annoying if it spams)
} catch (TypeLoadException) {
// silently ignore (can happen, is very annoying if it spams)
} catch (Exception e) {
ThreadUtility.LogException(new AggregateException(asm.FullName, e));
}
}
} catch (Exception e) {
ThreadUtility.LogException(e);
}
return methods;
}
private static void InvokeStaticMethod(MethodBase m, string attrName, List<MethodPatch> patchedMethods) {
try {
if (patchedMethods != null && m.GetParameters().Length == 1) {
m.Invoke(null, new object[] { patchedMethods });
} else {
m.Invoke(null, new object[] { });
}
} catch (Exception e) {
if (m.GetParameters().Length != 0) {
Log.Warning($"[{attrName}] {m.DeclaringType?.Name} {m.Name} {Localization.Translations.Utility.OnHotReloadWarning}\n{e}");
} else {
Log.Warning($"[{attrName}] {m.DeclaringType?.Name} {m.Name} failed. Exception\n{e}");
}
}
}
private static void InvokeInstanceMethod(MethodBase m, Object go, List<MethodPatch> patchedMethods) {
try {
if (patchedMethods != null && m.GetParameters().Length == 1) {
m.Invoke(go, new object[] { patchedMethods });
} else {
m.Invoke(go, new object[] { });
}
} catch (Exception e) {
if (m.GetParameters().Length != 0) {
Log.Warning($"[InvokeOnHotReload] {m.DeclaringType?.Name} {m.Name} {Localization.Translations.Utility.OnHotReloadWarning}\n{e}");
} else {
Log.Warning(string.Format(Localization.Translations.Logging.InvokeOnHotReloadFailed, m.DeclaringType?.Name, m.Name, e));
}
}
}
private static void InvokeInstanceMethodStatic(MethodBase m, Object go) {
try {
m.Invoke(null, new object[] { go });
} catch (Exception e) {
if (m.GetParameters().Length != 0) {
Log.Warning($"[InvokeOnHotReloadLocal] {m.DeclaringType?.Name} {m.Name} {Localization.Translations.Utility.OnHotReloadLocalCallWarning}\n{e}");
} else {
Log.Warning(Localization.Translations.Logging.InvokeOnHotReloadLocalFailed, m.DeclaringType?.Name, m.Name, e);
}
}
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 8fde7dfa45d340eda4c6c64ccf52e17d
timeCreated: 1673824017
AssetOrigin:
serializedVersion: 1
productId: 254358
packageName: Hot Reload | Edit Code Without Compiling
packageVersion: 1.13.17
assetPath: Packages/com.singularitygroup.hotreload/Runtime/OnHotReloadDispatch.cs
uploadId: 870414

Some files were not shown because too many files have changed in this diff Show More