added hotreload
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
#if ENABLE_MONO && (DEVELOPMENT_BUILD || UNITY_EDITOR)
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -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.7
|
||||
assetPath: Packages/com.singularitygroup.hotreload/Runtime/AppCallbackListener.cs
|
||||
uploadId: 752503
|
||||
171
Packages/com.singularitygroup.hotreload/Runtime/BuildInfo.cs
Normal file
171
Packages/com.singularitygroup.hotreload/Runtime/BuildInfo.cs
Normal file
@@ -0,0 +1,171 @@
|
||||
#if ENABLE_MONO && (DEVELOPMENT_BUILD || UNITY_EDITOR)
|
||||
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;
|
||||
|
||||
[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;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -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.7
|
||||
assetPath: Packages/com.singularitygroup.hotreload/Runtime/BuildInfo.cs
|
||||
uploadId: 752503
|
||||
@@ -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.7
|
||||
assetPath: Packages/com.singularitygroup.hotreload/Runtime/Burst/JobHotReloadUtility.cs
|
||||
uploadId: 752503
|
||||
@@ -0,0 +1,41 @@
|
||||
#if ENABLE_MONO && (DEVELOPMENT_BUILD || UNITY_EDITOR)
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -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.7
|
||||
assetPath: Packages/com.singularitygroup.hotreload/Runtime/BurstChecker.cs
|
||||
uploadId: 752503
|
||||
720
Packages/com.singularitygroup.hotreload/Runtime/CodePatcher.cs
Normal file
720
Packages/com.singularitygroup.hotreload/Runtime/CodePatcher.cs
Normal file
@@ -0,0 +1,720 @@
|
||||
#if ENABLE_MONO && (DEVELOPMENT_BUILD || UNITY_EDITOR)
|
||||
|
||||
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 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 readonly List<Tuple<SMethod, string>> patchFailures = new List<Tuple<SMethod, string>>();
|
||||
public readonly List<string> patchExceptions = new List<string>();
|
||||
}
|
||||
|
||||
class FieldHandler {
|
||||
public readonly Action<Type, FieldInfo> storeField;
|
||||
public readonly Action<Type, FieldInfo, FieldInfo> registerInspectorFieldAttributes;
|
||||
public readonly Func<Type, string, bool> hideField;
|
||||
|
||||
public FieldHandler(Action<Type, FieldInfo> storeField, Func<Type, string, bool> hideField, Action<Type, FieldInfo, FieldInfo> registerInspectorFieldAttributes) {
|
||||
this.storeField = storeField;
|
||||
this.hideField = hideField;
|
||||
this.registerInspectorFieldAttributes = registerInspectorFieldAttributes;
|
||||
}
|
||||
}
|
||||
|
||||
class CodePatcher {
|
||||
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;
|
||||
|
||||
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("Encountered exception when loading patches from disk:\n{0}", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
|
||||
static void InitializeUnityEvents() {
|
||||
UnityEventHelper.Initialize();
|
||||
}
|
||||
|
||||
|
||||
void LoadPatches(string filePath) {
|
||||
PlayerLog("Loading patches from file {0}", filePath);
|
||||
var file = new FileInfo(filePath);
|
||||
if(file.Exists) {
|
||||
var bytes = File.ReadAllText(filePath);
|
||||
var patches = JsonConvert.DeserializeObject<List<MethodPatchResponse>>(bytes);
|
||||
PlayerLog("Loaded {0} patches from disk", 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 RegisterPatchesResult RegisterPatches(MethodPatchResponse patches, bool persist) {
|
||||
PlayerLog("Register patches.\nWarnings: {0} \nMethods:\n{1}", 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("ApplyPatches. {0} patches pending.", 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("Exception occured when handling method patch. Exception:\n{0}", 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("A new Scene was loaded while new unity event methods were added at runtime. MonoBehaviours in the Scene will not trigger these new events.");
|
||||
didLog = true;
|
||||
};
|
||||
}
|
||||
|
||||
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);
|
||||
try {
|
||||
UnityEventHelper.EnsureUnityEventMethod(newMethod);
|
||||
} catch(Exception ex) {
|
||||
Log.Warning("Encountered exception in EnsureUnityEventMethod: {0} {1}", ex.GetType().Name, ex.Message);
|
||||
}
|
||||
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($"Edit requires full recompile to apply: Encountered exception when applying a patch.\nCommon causes: editing code that failed to patch previously, an unsupported change, or a real bug in Hot Reload.\nIf you think this is a bug, please report the issue on Discord and include a code-snippet before/after.\nException: {ex}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HandleRemovedUnityMethods(SMethod[] removedMethods) {
|
||||
if (removedMethods == null) {
|
||||
return;
|
||||
}
|
||||
foreach(var sMethod in removedMethods) {
|
||||
try {
|
||||
var oldMethod = SymbolResolver.Resolve(sMethod);
|
||||
UnityEventHelper.RemoveUnityEventMethod(oldMethod);
|
||||
} catch (SymbolResolvingFailedException) {
|
||||
// ignore, not a unity event method if can't resolve
|
||||
} catch(Exception ex) {
|
||||
Log.Warning("Encountered exception in RemoveUnityEventMethod: {0} {1}", ex.GetType().Name, ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Important: must come before applying any patches
|
||||
void RegisterNewFieldInitializers(MethodPatchResponse resp) {
|
||||
for (var i = 0; i < resp.addedFieldInitializerFields.Length; i++) {
|
||||
var sField = resp.addedFieldInitializerFields[i];
|
||||
var sMethod = resp.addedFieldInitializerInitializers[i];
|
||||
try {
|
||||
var declaringType = SymbolResolver.Resolve(sField.declaringType);
|
||||
var method = SymbolResolver.Resolve(sMethod);
|
||||
if (!(method is MethodInfo initializer)) {
|
||||
Log.Warning($"Failed registering initializer for field {sField.fieldName} in {sField.declaringType.typeName}. Field value might not be initialized correctly. Invalid method.");
|
||||
continue;
|
||||
}
|
||||
// We infer if the field is static by the number of parameters the method has
|
||||
// because sField is old field
|
||||
var isStatic = initializer.GetParameters().Length == 0;
|
||||
MethodUtils.DisableVisibilityChecks(initializer);
|
||||
// Initializer return type is used in place of fieldType because latter might be point to old field if the type changed
|
||||
FieldInitializerRegister.RegisterInitializer(declaringType, sField.fieldName, initializer.ReturnType, initializer, isStatic);
|
||||
|
||||
} catch (Exception e) {
|
||||
RequestHelper.RequestEditorEventWithRetry(new Stat(StatSource.Client, StatLevel.Error, StatFeature.Patching, StatEventType.RegisterFieldInitializer), new EditorExtraData {
|
||||
{ StatKey.PatchId, resp.id },
|
||||
{ StatKey.Detailed_Exception, e.ToString() },
|
||||
}).Forget();
|
||||
Log.Warning($"Failed registering initializer for field {sField.fieldName} in {sField.declaringType.typeName}. Field value might not be initialized correctly. Exception: {e.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RegisterNewFieldDefinitions(MethodPatchResponse resp) {
|
||||
foreach (var sField in resp.newFieldDefinitions) {
|
||||
try {
|
||||
var declaringType = SymbolResolver.Resolve(sField.declaringType);
|
||||
var fieldType = SymbolResolver.Resolve(sField).FieldType;
|
||||
FieldResolver.RegisterFieldType(declaringType, sField.fieldName, fieldType);
|
||||
} catch (Exception e) {
|
||||
RequestHelper.RequestEditorEventWithRetry(new Stat(StatSource.Client, StatLevel.Error, StatFeature.Patching, StatEventType.RegisterFieldDefinition), new EditorExtraData {
|
||||
{ StatKey.PatchId, resp.id },
|
||||
{ StatKey.Detailed_Exception, e.ToString() },
|
||||
}).Forget();
|
||||
Log.Warning($"Failed registering new field definitions for field {sField.fieldName} in {sField.declaringType.typeName}. Exception: {e.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Important: must come before applying any patches
|
||||
// Note: server might decide not to report removed field initializer at all if it can handle it
|
||||
void RemoveOldFieldInitializers(MethodPatchResponse resp) {
|
||||
foreach (var sField in resp.removedFieldInitializers) {
|
||||
try {
|
||||
var declaringType = SymbolResolver.Resolve(sField.declaringType);
|
||||
var fieldType = SymbolResolver.Resolve(sField.declaringType);
|
||||
FieldInitializerRegister.UnregisterInitializer(declaringType, sField.fieldName, fieldType, sField.isStatic);
|
||||
} catch (Exception e) {
|
||||
RequestHelper.RequestEditorEventWithRetry(new Stat(StatSource.Client, StatLevel.Error, StatFeature.Patching, StatEventType.UnregisterFieldInitializer), new EditorExtraData {
|
||||
{ StatKey.PatchId, resp.id },
|
||||
{ StatKey.Detailed_Exception, e.ToString() },
|
||||
}).Forget();
|
||||
Log.Warning($"Failed removing initializer for field {sField.fieldName} in {sField.declaringType.typeName}. Field value might not be initialized correctly. Exception: {e.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Important: must come before applying any patches
|
||||
// Should also come after RegisterNewFieldInitializers so that new initializers are not invoked for existing objects
|
||||
internal void HandleReshapedFields(MethodPatchResponse resp) {
|
||||
foreach(var patch in resp.patches) {
|
||||
var removedReshapedFields = patch.deletedFields;
|
||||
var renamedReshapedFieldsFrom = patch.renamedFieldsFrom;
|
||||
var renamedReshapedFieldsTo = patch.renamedFieldsTo;
|
||||
|
||||
foreach (var f in removedReshapedFields) {
|
||||
try {
|
||||
var declaringType = SymbolResolver.Resolve(f.declaringType);
|
||||
var fieldType = SymbolResolver.Resolve(f).FieldType;
|
||||
FieldResolver.ClearHolders(declaringType, f.isStatic, f.fieldName, fieldType);
|
||||
} catch (Exception e) {
|
||||
RequestHelper.RequestEditorEventWithRetry(new Stat(StatSource.Client, StatLevel.Error, StatFeature.Patching, StatEventType.ClearHolders), new EditorExtraData {
|
||||
{ StatKey.PatchId, resp.id },
|
||||
{ StatKey.Detailed_Exception, e.ToString() },
|
||||
}).Forget();
|
||||
Log.Warning($"Failed removing field value from {f.fieldName} in {f.declaringType.typeName}. Field value in code might not be up to date. Exception: {e.Message}");
|
||||
}
|
||||
}
|
||||
for (var i = 0; i < renamedReshapedFieldsFrom.Length; i++) {
|
||||
var fromField = renamedReshapedFieldsFrom[i];
|
||||
var toField = renamedReshapedFieldsTo[i];
|
||||
try {
|
||||
var declaringType = SymbolResolver.Resolve(fromField.declaringType);
|
||||
var fieldType = SymbolResolver.Resolve(fromField).FieldType;
|
||||
var toFieldType = SymbolResolver.Resolve(toField).FieldType;
|
||||
if (!AreSTypesCompatible(fromField.declaringType, toField.declaringType)
|
||||
|| fieldType != toFieldType
|
||||
|| fromField.isStatic != toField.isStatic
|
||||
) {
|
||||
FieldResolver.ClearHolders(declaringType, fromField.isStatic, fromField.fieldName, fieldType);
|
||||
continue;
|
||||
}
|
||||
FieldResolver.MoveHolders(declaringType, fromField.fieldName, toField.fieldName, fieldType, fromField.isStatic);
|
||||
} catch (Exception e) {
|
||||
RequestHelper.RequestEditorEventWithRetry(new Stat(StatSource.Client, StatLevel.Error, StatFeature.Patching, StatEventType.MoveHolders), new EditorExtraData {
|
||||
{ StatKey.PatchId, resp.id },
|
||||
{ StatKey.Detailed_Exception, e.ToString() },
|
||||
}).Forget();
|
||||
Log.Warning($"Failed moving field value from {fromField} to {toField} in {toField.declaringType.typeName}. Field value in code might not be up to date. Exception: {e.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal bool AreSTypesCompatible(SType one, SType two) {
|
||||
if (one.isGenericParameter != two.isGenericParameter) {
|
||||
return false;
|
||||
}
|
||||
if (one.metadataToken != two.metadataToken) {
|
||||
return false;
|
||||
}
|
||||
if (one.assemblyName != two.assemblyName) {
|
||||
return false;
|
||||
}
|
||||
if (one.genericParameterPosition != two.genericParameterPosition) {
|
||||
return false;
|
||||
}
|
||||
if (one.typeName != two.typeName) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
internal void RegisterInspectorFieldAttributes(RegisterPatchesResult result, MethodPatchResponse resp) {
|
||||
foreach (var patch in resp.patches) {
|
||||
var propertyAttributesFieldOriginal = patch.propertyAttributesFieldOriginal ?? Array.Empty<SField>();
|
||||
var propertyAttributesFieldUpdated = patch.propertyAttributesFieldUpdated ?? Array.Empty<SField>();
|
||||
for (var i = 0; i < propertyAttributesFieldOriginal.Length; i++) {
|
||||
var original = propertyAttributesFieldOriginal[i];
|
||||
var updated = propertyAttributesFieldUpdated[i];
|
||||
try {
|
||||
var declaringType = SymbolResolver.Resolve(original.declaringType);
|
||||
var originalField = SymbolResolver.Resolve(original);
|
||||
var updatedField = SymbolResolver.Resolve(updated);
|
||||
fieldHandler?.registerInspectorFieldAttributes?.Invoke(declaringType, originalField, updatedField);
|
||||
result.inspectorModified = true;
|
||||
} catch (Exception e) {
|
||||
RequestHelper.RequestEditorEventWithRetry(new Stat(StatSource.Client, StatLevel.Error, StatFeature.Patching, StatEventType.MoveHolders), new EditorExtraData {
|
||||
{ StatKey.PatchId, resp.id },
|
||||
{ StatKey.Detailed_Exception, e.ToString() },
|
||||
}).Forget();
|
||||
Log.Warning($"Failed updating field attributes of {original.fieldName} in {original.declaringType.typeName}. Updates might not reflect in the inspector. Exception: {e.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void HandleNewFields(string patchId, RegisterPatchesResult result, SField[] sFields) {
|
||||
foreach (var sField in sFields) {
|
||||
if (!sField.serializable) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
var declaringType = SymbolResolver.Resolve(sField.declaringType);
|
||||
var field = SymbolResolver.Resolve(sField);
|
||||
fieldHandler?.storeField?.Invoke(declaringType, field);
|
||||
result.inspectorModified = true;
|
||||
} catch (Exception e) {
|
||||
RequestHelper.RequestEditorEventWithRetry(new Stat(StatSource.Client, StatLevel.Error, StatFeature.Patching, StatEventType.AddInspectorField), new EditorExtraData {
|
||||
{ StatKey.PatchId, patchId },
|
||||
{ StatKey.Detailed_Exception, e.ToString() },
|
||||
}).Forget();
|
||||
Log.Warning($"Failed adding field {sField.fieldName}:{sField.declaringType.typeName} to the inspector. Field will not be displayed. Exception: {e.Message}");
|
||||
}
|
||||
}
|
||||
result.addedFields.AddRange(sFields);
|
||||
}
|
||||
|
||||
// IMPORTANT: must come before HandleNewFields. Might contain new fields which we don't want to hide
|
||||
internal void HandleAlteredFields(string patchId, RegisterPatchesResult result, SField[] alteredFields) {
|
||||
if (alteredFields == null) {
|
||||
return;
|
||||
}
|
||||
bool alteredFieldHidden = false;
|
||||
foreach(var sField in alteredFields) {
|
||||
try {
|
||||
var declaringType = SymbolResolver.Resolve(sField.declaringType);
|
||||
if (fieldHandler?.hideField?.Invoke(declaringType, sField.fieldName) == true) {
|
||||
alteredFieldHidden = true;
|
||||
}
|
||||
} catch(Exception e) {
|
||||
RequestHelper.RequestEditorEventWithRetry(new Stat(StatSource.Client, StatLevel.Error, StatFeature.Patching, StatEventType.HideInspectorField), new EditorExtraData {
|
||||
{ StatKey.PatchId, patchId },
|
||||
{ StatKey.Detailed_Exception, e.ToString() },
|
||||
}).Forget();
|
||||
Log.Warning($"Failed hiding field {sField.fieldName}:{sField.declaringType.typeName} from the inspector. Exception: {e.Message}");
|
||||
}
|
||||
}
|
||||
if (alteredFieldHidden) {
|
||||
result.inspectorModified = true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
Dictionary<MethodBase, MethodBase> previousPatchMethods = new Dictionary<MethodBase, MethodBase>();
|
||||
public IEnumerable<MethodBase> OriginalPatchMethods => previousPatchMethods.Keys;
|
||||
List<MethodBase> newMethods = new List<MethodBase>();
|
||||
|
||||
string PatchMethod(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 "Patching methods is not allowed while the Debugger is attached. You can change this behavior in settings if Hot Reload is compatible with the debugger you're running.";
|
||||
}
|
||||
|
||||
if (DateTime.UtcNow - start > TimeSpan.FromMilliseconds(500)) {
|
||||
Log.Info("Hot Reload apply took {0}", (DateTime.UtcNow - start).TotalMilliseconds);
|
||||
}
|
||||
|
||||
if(state.match == null) {
|
||||
var error = "Edit requires full recompile to apply: Method mismatch: {0}, patch: {1}. \nCommon causes: editing code that failed to patch previously, an unsupported change, or a real bug in Hot Reload.\nIf you think this is a bug, please report the issue on Discord and include a code-snippet before/after.";
|
||||
RequestHelper.RequestEditorEventWithRetry(new Stat(StatSource.Client, StatLevel.Error, StatFeature.Patching, StatEventType.MethodMismatch), new EditorExtraData {
|
||||
{ StatKey.PatchId, patchId },
|
||||
}).Forget();
|
||||
return string.Format(error, sOriginalMethod.simpleName, patchMethod.Name);
|
||||
}
|
||||
|
||||
PlayerLog("Detour method {0:X8} {1}, offset: {2}", 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 $"Edit requires full recompile to apply: Failed to apply patch for method {method.displayName} in assembly {method.assemblyName}.\nCommon causes: editing code that failed to patch previously, an unsupported change, or a real bug in Hot Reload.\nIf you think this is a bug, please report the issue on Discord and include a code-snippet before/after.\nException: {exception}";
|
||||
}
|
||||
|
||||
void EnsureSymbolResolver() {
|
||||
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("Invalid path: " + filePath, nameof(filePath));
|
||||
}
|
||||
Directory.CreateDirectory(dir);
|
||||
var history = patchHistory.ToList();
|
||||
|
||||
PlayerLog("Saving {0} applied patches to {1}", 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(string filePath) {
|
||||
seenResponses.Clear();
|
||||
var file = new FileInfo(filePath);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -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.7
|
||||
assetPath: Packages/com.singularitygroup.hotreload/Runtime/CodePatcher.cs
|
||||
uploadId: 752503
|
||||
@@ -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:
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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.7
|
||||
assetPath: Packages/com.singularitygroup.hotreload/Runtime/Demo/Scenes/HotReloadBasicDemo.unity
|
||||
uploadId: 752503
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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.7
|
||||
assetPath: Packages/com.singularitygroup.hotreload/Runtime/Demo/Scenes/HotReloadBurstDemo.unity
|
||||
uploadId: 752503
|
||||
@@ -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.7
|
||||
assetPath: Packages/com.singularitygroup.hotreload/Runtime/Demo/Scenes/HotReloadBurstDemoSettings.lighting
|
||||
uploadId: 752503
|
||||
@@ -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 = "Hot Reload is running";
|
||||
} else {
|
||||
informationText.text = "Hot Reload is not running";
|
||||
}
|
||||
|
||||
// // 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.7
|
||||
assetPath: Packages/com.singularitygroup.hotreload/Runtime/Demo/Scripts/HotReloadBasicDemo.cs
|
||||
uploadId: 752503
|
||||
@@ -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 = "Hot Reload is running";
|
||||
} else {
|
||||
informationText.text = "Hot Reload is not running";
|
||||
}
|
||||
}
|
||||
|
||||
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.7
|
||||
assetPath: Packages/com.singularitygroup.hotreload/Runtime/Demo/Scripts/HotReloadBurstJobsDemo.cs
|
||||
uploadId: 752503
|
||||
@@ -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.7
|
||||
assetPath: Packages/com.singularitygroup.hotreload/Runtime/Demo/Scripts/IDemo.cs
|
||||
uploadId: 752503
|
||||
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.7
|
||||
assetPath: Packages/com.singularitygroup.hotreload/Runtime/HotReloadPrompts.prefab
|
||||
uploadId: 752503
|
||||
@@ -0,0 +1,140 @@
|
||||
#if ENABLE_MONO && (DEVELOPMENT_BUILD || UNITY_EDITOR)
|
||||
using System;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using System.IO;
|
||||
using UnityEngine;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
#endif
|
||||
|
||||
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) {
|
||||
#if UNITY_EDITOR
|
||||
settings = AssetDatabase.LoadAssetAtPath<HotReloadSettingsObject>(editorAssetPath);
|
||||
#else
|
||||
settings = null;
|
||||
#endif
|
||||
} 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() {
|
||||
#if UNITY_EDITOR
|
||||
var prefab = AssetDatabase.LoadAssetAtPath<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<GameObject>(promptsPrefabPath);
|
||||
}
|
||||
}
|
||||
if (prefab == null) {
|
||||
throw new Exception("Failed to find PromptsPrefab (are you using Hot Reload as a package?");
|
||||
}
|
||||
PromptsPrefab = prefab;
|
||||
#endif
|
||||
}
|
||||
|
||||
public void EnsurePrefabNotInBuild() {
|
||||
#if UNITY_EDITOR
|
||||
PromptsPrefab = null;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
// put the stored settings here
|
||||
|
||||
[Header("Build Settings")]
|
||||
[Tooltip("Should the Hot Reload runtime be included in development builds? HotReload is never included in release builds.")]
|
||||
public bool IncludeInBuild = true;
|
||||
|
||||
[Header("Player Settings")]
|
||||
public bool AllowAndroidAppToMakeHttpRequests = false;
|
||||
|
||||
#region hidden
|
||||
|
||||
/// Reference to the Prefab, for loading it at runtime
|
||||
[HideInInspector]
|
||||
public GameObject PromptsPrefab;
|
||||
#endregion
|
||||
|
||||
#endregion settings
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -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.7
|
||||
assetPath: Packages/com.singularitygroup.hotreload/Runtime/HotReloadSettingsObject.cs
|
||||
uploadId: 752503
|
||||
@@ -0,0 +1,19 @@
|
||||
#if ENABLE_MONO && (DEVELOPMENT_BUILD || UNITY_EDITOR)
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -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.7
|
||||
assetPath: Packages/com.singularitygroup.hotreload/Runtime/HttpClientUtils.cs
|
||||
uploadId: 752503
|
||||
@@ -0,0 +1,11 @@
|
||||
#if ENABLE_MONO && (DEVELOPMENT_BUILD || UNITY_EDITOR)
|
||||
namespace SingularityGroup.HotReload {
|
||||
public interface IServerHealthCheck {
|
||||
bool IsServerHealthy { get; }
|
||||
}
|
||||
|
||||
internal interface IServerHealthCheckInternal : IServerHealthCheck {
|
||||
void CheckHealth();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -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.7
|
||||
assetPath: Packages/com.singularitygroup.hotreload/Runtime/IServerHealthCheck.cs
|
||||
uploadId: 752503
|
||||
@@ -0,0 +1,27 @@
|
||||
#if ENABLE_MONO && (DEVELOPMENT_BUILD || UNITY_EDITOR)
|
||||
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?
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -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.7
|
||||
assetPath: Packages/com.singularitygroup.hotreload/Runtime/InstallQRDialog.cs
|
||||
uploadId: 752503
|
||||
64
Packages/com.singularitygroup.hotreload/Runtime/IpHelper.cs
Normal file
64
Packages/com.singularitygroup.hotreload/Runtime/IpHelper.cs
Normal file
@@ -0,0 +1,64 @@
|
||||
#if ENABLE_MONO && (DEVELOPMENT_BUILD || UNITY_EDITOR)
|
||||
#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;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -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.7
|
||||
assetPath: Packages/com.singularitygroup.hotreload/Runtime/IpHelper.cs
|
||||
uploadId: 752503
|
||||
@@ -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:
|
||||
Binary file not shown.
@@ -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.7
|
||||
assetPath: Packages/com.singularitygroup.hotreload/Runtime/Libs/EditorOnly/SingularityGroup.HotReload.RuntimeDependencies.dll
|
||||
uploadId: 752503
|
||||
Binary file not shown.
@@ -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.7
|
||||
assetPath: Packages/com.singularitygroup.hotreload/Runtime/Libs/EditorOnly/SingularityGroup.HotReload.RuntimeDependencies2019.dll
|
||||
uploadId: 752503
|
||||
Binary file not shown.
@@ -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.7
|
||||
assetPath: Packages/com.singularitygroup.hotreload/Runtime/Libs/EditorOnly/SingularityGroup.HotReload.RuntimeDependencies2020.dll
|
||||
uploadId: 752503
|
||||
Binary file not shown.
@@ -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.7
|
||||
assetPath: Packages/com.singularitygroup.hotreload/Runtime/Libs/EditorOnly/SingularityGroup.HotReload.RuntimeDependencies2022.dll
|
||||
uploadId: 752503
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4b3d6360d6d1f2c47b659f0a4960ebfe
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
@@ -0,0 +1,95 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 15528e9db0a6c9b45a66378f0b6c4dd6
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints:
|
||||
- ENABLE_MONO
|
||||
- DEVELOPMENT_BUILD
|
||||
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.7
|
||||
assetPath: Packages/com.singularitygroup.hotreload/Runtime/Libs/OnDevice/SingularityGroup.HotReload.RuntimeDependencies.dll
|
||||
uploadId: 752503
|
||||
Binary file not shown.
@@ -0,0 +1,96 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4febf8334e6a82f4e9faf3513c7fcc8d
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints:
|
||||
- ENABLE_MONO
|
||||
- UNITY_2019_4_OR_NEWER
|
||||
- DEVELOPMENT_BUILD
|
||||
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.7
|
||||
assetPath: Packages/com.singularitygroup.hotreload/Runtime/Libs/OnDevice/SingularityGroup.HotReload.RuntimeDependencies2019.dll
|
||||
uploadId: 752503
|
||||
Binary file not shown.
@@ -0,0 +1,96 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 35c3cec01c5230e41bea51d3ac6fcfa1
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints:
|
||||
- ENABLE_MONO
|
||||
- UNITY_2020_3_OR_NEWER
|
||||
- DEVELOPMENT_BUILD
|
||||
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.7
|
||||
assetPath: Packages/com.singularitygroup.hotreload/Runtime/Libs/OnDevice/SingularityGroup.HotReload.RuntimeDependencies2020.dll
|
||||
uploadId: 752503
|
||||
Binary file not shown.
@@ -0,0 +1,96 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c9f10603236554c4896f310072d57f24
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints:
|
||||
- ENABLE_MONO
|
||||
- UNITY_2022_2_OR_NEWER
|
||||
- DEVELOPMENT_BUILD
|
||||
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.7
|
||||
assetPath: Packages/com.singularitygroup.hotreload/Runtime/Libs/OnDevice/SingularityGroup.HotReload.RuntimeDependencies2022.dll
|
||||
uploadId: 752503
|
||||
@@ -0,0 +1,116 @@
|
||||
#if ENABLE_MONO && (DEVELOPMENT_BUILD || UNITY_EDITOR)
|
||||
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using SingularityGroup.HotReload.MonoMod.Utils;
|
||||
|
||||
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 "unknown issue";
|
||||
}
|
||||
|
||||
static string AreMethodBasesCompatible(MethodBase previousMethod, MethodBase patchMethod) {
|
||||
if(previousMethod.Name != patchMethod.Name) {
|
||||
return "Method name mismatch";
|
||||
}
|
||||
//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 "Declaring type name mismatch";
|
||||
}
|
||||
//Check in case type parameter overloads to distinguish between: void M<T>() { } <-> void M() { }
|
||||
if(previousMethod.IsGenericMethodDefinition != patchMethod.IsGenericMethodDefinition) {
|
||||
return "IsGenericMethodDefinition mismatch";
|
||||
}
|
||||
|
||||
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 "missing this parameter";
|
||||
}
|
||||
//this parameter has to be the declaring type
|
||||
if(!ParamTypeMatches(patchParams[0].ParameterType, previousMethod.DeclaringType)) {
|
||||
return "this parameter type mismatch";
|
||||
}
|
||||
//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 "parameter count mismatch";
|
||||
}
|
||||
for (var i = 0; i < x.Count; i++) {
|
||||
if(x.Array[i + x.Offset].ParameterType != y.Array[i + y.Offset].ParameterType) {
|
||||
return "parameter type mismatch";
|
||||
}
|
||||
}
|
||||
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 : "Return type mismatch");
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -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.7
|
||||
assetPath: Packages/com.singularitygroup.hotreload/Runtime/MethodCompatiblity.cs
|
||||
uploadId: 752503
|
||||
@@ -0,0 +1,716 @@
|
||||
#if ENABLE_MONO && (DEVELOPMENT_BUILD || UNITY_EDITOR)
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
#endif
|
||||
@@ -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.7
|
||||
assetPath: Packages/com.singularitygroup.hotreload/Runtime/MethodPatchResponsesConverter.cs
|
||||
uploadId: 752503
|
||||
@@ -0,0 +1,35 @@
|
||||
#if ENABLE_MONO && (DEVELOPMENT_BUILD || UNITY_EDITOR)
|
||||
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
|
||||
}
|
||||
}
|
||||
#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.7
|
||||
assetPath: Packages/com.singularitygroup.hotreload/Runtime/MethodUtils.cs
|
||||
uploadId: 752503
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a2fd781ae18045f0b7690cd490737996
|
||||
timeCreated: 1675064423
|
||||
@@ -0,0 +1,80 @@
|
||||
#if ENABLE_MONO && (DEVELOPMENT_BUILD || UNITY_EDITOR)
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace SingularityGroup.HotReload {
|
||||
internal class ConnectionDialog : MonoBehaviour {
|
||||
[Header("UI controls")]
|
||||
public Button buttonHide;
|
||||
|
||||
[Header("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 = ConnectionSummary.Connected) {
|
||||
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 = $"Patches: {pendingPatches} pending, {patchesApplied} applied";
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 const string Cancelled = "Cancelled";
|
||||
public const string Connecting = "Connecting ...";
|
||||
public const string Handshaking = "Handshaking ...";
|
||||
public const string DifferencesFound = "Differences found";
|
||||
public const string Connected = "Connected!";
|
||||
// reconnecting can be shown for a long time, so a longer message is okay
|
||||
public const string TryingToReconnect = "Trying to reconnect ...";
|
||||
public const string Disconnected = "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.7
|
||||
assetPath: Packages/com.singularitygroup.hotreload/Runtime/MonoBehaviours/ConnectionDialog.cs
|
||||
uploadId: 752503
|
||||
@@ -0,0 +1,145 @@
|
||||
#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("Other")]
|
||||
[Tooltip("Used when project does not create an EventSystem early enough")]
|
||||
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("IsPlayerWithHotReload() is false");
|
||||
}
|
||||
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($"No EventSystem is active, enabling an EventSystem inside Hot Reload {name} prefab." +
|
||||
" A Unity EventSystem and an Input module is required for tapping buttons on the Unity UI.");
|
||||
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.7
|
||||
assetPath: Packages/com.singularitygroup.hotreload/Runtime/MonoBehaviours/Prompts.cs
|
||||
uploadId: 752503
|
||||
@@ -0,0 +1,64 @@
|
||||
#if ENABLE_MONO && (DEVELOPMENT_BUILD || UNITY_EDITOR)
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace SingularityGroup.HotReload {
|
||||
class QuestionDialog : MonoBehaviour {
|
||||
|
||||
[Header("Information")]
|
||||
public Text textSummary;
|
||||
public Text textSuggestion;
|
||||
|
||||
[Header("UI controls")]
|
||||
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 = "Continue";
|
||||
public string cancelButtonText = "Cancel";
|
||||
public string moreInfoUrl = "https://hotreload.net/documentation#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.7
|
||||
assetPath: Packages/com.singularitygroup.hotreload/Runtime/MonoBehaviours/QuestionDialog.cs
|
||||
uploadId: 752503
|
||||
@@ -0,0 +1,104 @@
|
||||
#if ENABLE_MONO && (DEVELOPMENT_BUILD || UNITY_EDITOR)
|
||||
using JetBrains.Annotations;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace SingularityGroup.HotReload {
|
||||
internal class RetryDialog : MonoBehaviour {
|
||||
[Header("UI controls")]
|
||||
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(() => {
|
||||
Application.OpenURL("https://hotreload.net/documentation#connection-issues");
|
||||
});
|
||||
}
|
||||
|
||||
[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 = $"Is this device connected to {networkText}?";
|
||||
var waitForCompiling = "Wait for compiling to finish before trying again";
|
||||
var targetNetworkIsReachable = $"Make sure you're on the same {networkText} network. Also ensure Hot Reload is running";
|
||||
|
||||
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 ? "Auto-pair encountered an issue" : "Connection failed";
|
||||
|
||||
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.7
|
||||
assetPath: Packages/com.singularitygroup.hotreload/Runtime/MonoBehaviours/RetryDialog.cs
|
||||
uploadId: 752503
|
||||
196
Packages/com.singularitygroup.hotreload/Runtime/MonoMethod.cs
Normal file
196
Packages/com.singularitygroup.hotreload/Runtime/MonoMethod.cs
Normal file
@@ -0,0 +1,196 @@
|
||||
#if ENABLE_MONO && (DEVELOPMENT_BUILD || UNITY_EDITOR)
|
||||
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
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -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.7
|
||||
assetPath: Packages/com.singularitygroup.hotreload/Runtime/MonoMethod.cs
|
||||
uploadId: 752503
|
||||
8
Packages/com.singularitygroup.hotreload/Runtime/OSX.meta
Normal file
8
Packages/com.singularitygroup.hotreload/Runtime/OSX.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 62fd71232a4784cdeb53a6ab67694087
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
@@ -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.7
|
||||
assetPath: Packages/com.singularitygroup.hotreload/Runtime/OSX/HotReloadNativeHelper.dylib
|
||||
uploadId: 752503
|
||||
@@ -0,0 +1,194 @@
|
||||
#if ENABLE_MONO && (DEVELOPMENT_BUILD || UNITY_EDITOR)
|
||||
#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;
|
||||
#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} failed. Make sure it's a method with 0 parameters either static or defined on MonoBehaviour.");
|
||||
}
|
||||
}
|
||||
|
||||
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)}] failed to find method {attrib.methodToInvoke}. Make sure it exists within the same class.");
|
||||
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} failed. Make sure it's a method with 0 parameters either static or defined on MonoBehaviour.");
|
||||
}
|
||||
}
|
||||
|
||||
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} failed. Make sure it has 0 parameters, or 1 parameter with type List<MethodPatch>. Exception:\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} failed. Make sure it has 0 parameters, or 1 parameter with type List<MethodPatch>. Exception:\n{e}");
|
||||
} else {
|
||||
Log.Warning($"[InvokeOnHotReload] {m.DeclaringType?.Name} {m.Name} failed. Exception:\n{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} failed. Make sure it has 0 parameters. Exception:\n{e}");
|
||||
} else {
|
||||
Log.Warning($"[InvokeOnHotReloadLocal] {m.DeclaringType?.Name} {m.Name} failed. Exception:\n{e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -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.7
|
||||
assetPath: Packages/com.singularitygroup.hotreload/Runtime/OnHotReloadDispatch.cs
|
||||
uploadId: 752503
|
||||
@@ -0,0 +1,20 @@
|
||||
#if ENABLE_MONO && (DEVELOPMENT_BUILD || UNITY_EDITOR)
|
||||
using UnityEngine;
|
||||
|
||||
namespace SingularityGroup.HotReload {
|
||||
internal static class PackageConst {
|
||||
//CI changes this property to 'true' for asset store builds.
|
||||
//Don't touch unless you know what you are doing
|
||||
public static bool IsAssetStoreBuild => true;
|
||||
|
||||
|
||||
public const string Version = "1.13.7";
|
||||
// Never higher than Version
|
||||
// Used for the download
|
||||
public const string ServerVersion = "1.13.7";
|
||||
public const string PackageName = "com.singularitygroup.hotreload";
|
||||
public const string LibraryCachePath = "Library/" + PackageName;
|
||||
public const string ConfigFileName = "hot-reload-config.json";
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 877e785c22c57be45a4d79c1be346c79
|
||||
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.7
|
||||
assetPath: Packages/com.singularitygroup.hotreload/Runtime/PackageConst.cs
|
||||
uploadId: 752503
|
||||
@@ -0,0 +1,41 @@
|
||||
#if ENABLE_MONO && (DEVELOPMENT_BUILD || UNITY_EDITOR)
|
||||
using System;
|
||||
using SingularityGroup.HotReload.Newtonsoft.Json;
|
||||
using UnityEngine;
|
||||
|
||||
namespace SingularityGroup.HotReload {
|
||||
[Serializable]
|
||||
class PatchServerInfo {
|
||||
public readonly string hostName;
|
||||
public readonly int port;
|
||||
public readonly string commitHash;
|
||||
public readonly string rootPath;
|
||||
[Obsolete]public readonly bool isRemote;
|
||||
public readonly string customRequestOrigin;
|
||||
|
||||
public const string UnknownCommitHash = "unknown";
|
||||
|
||||
/// <param name="hostName">an ip address or "localhost"</param>
|
||||
public PatchServerInfo(string hostName, string commitHash, string rootPath) {
|
||||
this.hostName = hostName;
|
||||
this.commitHash = commitHash ?? UnknownCommitHash;
|
||||
this.rootPath = rootPath;
|
||||
this.port = RequestHelper.defaultPort;
|
||||
}
|
||||
|
||||
/// <param name="hostName">an ip address or "localhost"</param>
|
||||
// constructor should (must?) have a param for each field
|
||||
[JsonConstructor]
|
||||
public PatchServerInfo(string hostName, int port, string commitHash, string rootPath, bool isRemote = false, string customRequestOrigin = null) {
|
||||
this.hostName = hostName;
|
||||
this.port = port;
|
||||
this.commitHash = commitHash ?? UnknownCommitHash;
|
||||
this.rootPath = rootPath;
|
||||
#pragma warning disable CS0612 // Type or member is obsolete
|
||||
this.isRemote = isRemote;
|
||||
#pragma warning restore CS0612 // Type or member is obsolete
|
||||
this.customRequestOrigin = customRequestOrigin;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5795d77991613ae48870b7c349491adc
|
||||
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.7
|
||||
assetPath: Packages/com.singularitygroup.hotreload/Runtime/PatchServerInfo.cs
|
||||
uploadId: 752503
|
||||
@@ -0,0 +1,15 @@
|
||||
#if ENABLE_MONO && (DEVELOPMENT_BUILD || UNITY_EDITOR)
|
||||
using System.IO;
|
||||
|
||||
namespace SingularityGroup.HotReload {
|
||||
static class PersistencePaths {
|
||||
public static string GetPatchesFilePath(string basePath) {
|
||||
return Path.Combine(basePath, "CodePatcher", "patches.bin");
|
||||
}
|
||||
|
||||
public static string GetServerInfoFilePath(string basePath) {
|
||||
return Path.Combine(basePath, "CodePatcher", "hostInfo.json");
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3742023437057a049842c5fad1019132
|
||||
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.7
|
||||
assetPath: Packages/com.singularitygroup.hotreload/Runtime/PersistencePaths.cs
|
||||
uploadId: 752503
|
||||
@@ -0,0 +1,117 @@
|
||||
#if ENABLE_MONO && (DEVELOPMENT_BUILD || UNITY_EDITOR)
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SingularityGroup.HotReload.DTO;
|
||||
|
||||
namespace SingularityGroup.HotReload {
|
||||
|
||||
static class PlayerCodePatcher {
|
||||
static Timer timer;
|
||||
|
||||
static PlayerCodePatcher() {
|
||||
if (PlayerEntrypoint.IsPlayerWithHotReload()) {
|
||||
timer = new Timer(OnIntervalThreaded, (Action) OnIntervalMainThread, 500, 500);
|
||||
serverHealthyAt = DateTime.MinValue;
|
||||
}
|
||||
}
|
||||
|
||||
private static DateTime serverHealthyAt;
|
||||
private static TimeSpan TimeSinceServerHealthy() => DateTime.UtcNow - serverHealthyAt;
|
||||
|
||||
/// <summary>
|
||||
/// Set server that you want to try connect to.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This allows repetitions of:
|
||||
/// - try handshake
|
||||
/// - success -> try healthcheck
|
||||
/// - success -> poll method patches
|
||||
/// -
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Only do this after confirming (with /handshake) that server is compatible with this build.<br/>
|
||||
/// The user will be prompted if handshake needs confirmation.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
internal static Task<ServerHandshake.Result> UpdateHost(PatchServerInfo serverInfo) {
|
||||
Log.Debug($"UpdateHost to {(serverInfo == null ? "null" : serverInfo.hostName)}");
|
||||
// In player builds, server is remote, se we don't load assemblies from any paths
|
||||
RequestHelper.ChangeAssemblySearchPaths(Array.Empty<string>());
|
||||
ServerHealthCheck.I.SetServerInfo(null); // stop doing health check on old server
|
||||
RequestHelper.SetServerInfo(serverInfo);
|
||||
// Show feedback about connection progress (handshake can take ~5 seconds for our big game)
|
||||
if (serverInfo == null) {
|
||||
Prompts.SetConnectionState(ConnectionSummary.Disconnected);
|
||||
} else {
|
||||
Prompts.SetConnectionState(ConnectionSummary.Connected);
|
||||
Prompts.ShowConnectionDialog();
|
||||
}
|
||||
return ServerHandshake.I.SetServerInfo(serverInfo);
|
||||
}
|
||||
|
||||
public static Task Disconnect() => UpdateHost(null);
|
||||
|
||||
static void OnIntervalThreaded(object o) {
|
||||
ServerHandshake.I.CheckHandshake();
|
||||
ServerHealthCheck.I.CheckHealthAsync().Forget();
|
||||
|
||||
ThreadUtility.RunOnMainThread((Action)o);
|
||||
}
|
||||
|
||||
static string lastPatchId = string.Empty;
|
||||
static void OnIntervalMainThread() {
|
||||
PatchServerInfo verifiedServer;
|
||||
if(ServerHandshake.I.TryGetVerifiedServer(out verifiedServer)) {
|
||||
// now that handshake verified, we are connected.
|
||||
// Note: If there is delay between handshake done and chosing to connect, then it may be outdated.
|
||||
Prompts.SetConnectionState(ConnectionSummary.Connecting);
|
||||
// Note: verified does not imply that server is running, sometimes we verify the host just from the deeplink data
|
||||
ServerHealthCheck.I.SetServerInfo(verifiedServer);
|
||||
}
|
||||
|
||||
if(ServerHealthCheck.I.IsServerHealthy) {
|
||||
// we may have reconnected to the same host, after losing connection for several seconds
|
||||
Prompts.SetConnectionState(ConnectionSummary.Connected, false);
|
||||
serverHealthyAt = DateTime.UtcNow;
|
||||
RequestHelper.PollMethodPatches(lastPatchId, resp => HandleResponseReceived(resp));
|
||||
} else if (ServerHealthCheck.I.WasServerResponding) { // only update prompt state if disconnected server
|
||||
var secondsSinceHealthy = TimeSinceServerHealthy().TotalSeconds;
|
||||
var reconnectTimeout = 30; // seconds
|
||||
if (secondsSinceHealthy > 2) {
|
||||
Log.Info("Hot Reload was unreachable for 5 seconds, trying to reconnect...");
|
||||
// feedback for the user so they know why patches are not applying
|
||||
Prompts.SetConnectionState($"{ConnectionSummary.TryingToReconnect} {reconnectTimeout - secondsSinceHealthy:F0}s", false);
|
||||
Prompts.ShowConnectionDialog();
|
||||
}
|
||||
if (secondsSinceHealthy > reconnectTimeout) {
|
||||
// give up on the server, give user a way to connect to another
|
||||
Log.Info($"Hot Reload was unreachable for {reconnectTimeout} seconds, disconnecting");
|
||||
var disconnectedServer = RequestHelper.ServerInfo;
|
||||
Disconnect().Forget();
|
||||
// Let user tap button to retry connecting to the same server (maybe just need to run Hot Reload again)
|
||||
// Assumption: prompt also has a way to connect to a different server
|
||||
Prompts.ShowRetryDialog(disconnectedServer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void HandleResponseReceived(MethodPatchResponse response) {
|
||||
Log.Debug("PollMethodPatches handling MethodPatchResponse id:{0} response.patches.Length:{1} response.failures.Length:{2}",
|
||||
response.id, response.patches.Length, response.failures.Length);
|
||||
if(response.patches.Length > 0) {
|
||||
CodePatcher.I.RegisterPatches(response, persist: true);
|
||||
}
|
||||
if(response.failures.Length > 0) {
|
||||
foreach (var failure in response.failures) {
|
||||
// feedback to user so they know why their patch wasn't applied
|
||||
Log.Warning(failure);
|
||||
}
|
||||
}
|
||||
lastPatchId = response.id;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 41b853a967284f44b36c071abf30124c
|
||||
timeCreated: 1674453262
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 254358
|
||||
packageName: Hot Reload | Edit Code Without Compiling
|
||||
packageVersion: 1.13.7
|
||||
assetPath: Packages/com.singularitygroup.hotreload/Runtime/PlayerCodePatcher.cs
|
||||
uploadId: 752503
|
||||
@@ -0,0 +1,161 @@
|
||||
#if ENABLE_MONO && (DEVELOPMENT_BUILD || UNITY_EDITOR)
|
||||
#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.Threading.Tasks;
|
||||
#if MOBILE_ANDROID
|
||||
// not able to use File apis for reading from StreamingAssets
|
||||
using UnityEngine.Networking;
|
||||
#endif
|
||||
using UnityEngine;
|
||||
using Debug = UnityEngine.Debug;
|
||||
using System.IO;
|
||||
|
||||
namespace SingularityGroup.HotReload {
|
||||
// entrypoint for Unity Player builds. Not necessary in Unity Editor.
|
||||
internal static class PlayerEntrypoint {
|
||||
/// Set when behaviour is created, when you access this instance through the singleton,
|
||||
/// you can assume that this field is not null.
|
||||
/// <remarks>
|
||||
/// In Player code you can assume this is set.<br/>
|
||||
/// When in Editor this is usually null.
|
||||
/// </remarks>
|
||||
static BuildInfo buildInfo { get; set; }
|
||||
|
||||
/// In Player code you can assume this is set (not null)
|
||||
public static BuildInfo PlayerBuildInfo => buildInfo;
|
||||
|
||||
#if ENABLE_MONO
|
||||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)]
|
||||
#endif
|
||||
private static void InitOnAppLoad() {
|
||||
AppCallbackListener.Init(); // any platform might be using this
|
||||
UnityHelper.Init();
|
||||
bool onlyPrefabMissing;
|
||||
if (!IsPlayerWithHotReload(out onlyPrefabMissing)) {
|
||||
if (onlyPrefabMissing) {
|
||||
Log.Warning("Hot Reload is not available in this build because one or more build settings were not supported.");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
TryAutoConnect().Forget();
|
||||
}
|
||||
|
||||
static async Task TryAutoConnect() {
|
||||
try {
|
||||
buildInfo = await GetBuildInfo();
|
||||
} catch (Exception e) {
|
||||
if (e is IOException) {
|
||||
Log.Warning("Hot Reload is not available in this build because one or more build settings were not supported.");
|
||||
} else {
|
||||
Log.Error($"Uknown exception happened when reading build info\n{e.GetType().Name}: {e.Message}");
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (buildInfo == null) {
|
||||
Log.Error($"Uknown issue happened when reading build info.");
|
||||
return;
|
||||
}
|
||||
|
||||
CodePatcher.I.debuggerCompatibilityEnabled = true;
|
||||
|
||||
try {
|
||||
var customIp = PlayerPrefs.GetString("HotReloadRuntime.CustomIP", "");
|
||||
if (!string.IsNullOrEmpty(customIp)) {
|
||||
buildInfo.buildMachineHostName = customIp;
|
||||
}
|
||||
var customPort = PlayerPrefs.GetString("HotReloadRuntime.CustomPort", "");
|
||||
if (!string.IsNullOrEmpty(customPort)) {
|
||||
buildInfo.buildMachinePort = int.Parse(customPort);
|
||||
}
|
||||
|
||||
if (buildInfo.BuildMachineServer == null) {
|
||||
Prompts.ShowRetryDialog(null);
|
||||
} else {
|
||||
// try reach server running on the build machine.
|
||||
TryConnect(buildInfo.BuildMachineServer, auto: true).Forget();
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
Log.Exception(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public static Task TryConnectToIpAndPort(string ip, int port) {
|
||||
ip = ip.Trim();
|
||||
if (buildInfo == null) {
|
||||
throw new ArgumentException("Build info not found");
|
||||
}
|
||||
buildInfo.buildMachineHostName = ip;
|
||||
buildInfo.buildMachinePort = port;
|
||||
PlayerPrefs.SetString("HotReloadRuntime.CustomIP", ip);
|
||||
PlayerPrefs.SetString("HotReloadRuntime.CustomPort", port.ToString());
|
||||
return TryConnect(buildInfo.BuildMachineServer, auto: false);
|
||||
}
|
||||
|
||||
public static async Task TryConnect(PatchServerInfo serverInfo, bool auto) {
|
||||
// try reach server running on the build machine.
|
||||
var handshake = PlayerCodePatcher.UpdateHost(serverInfo);
|
||||
await Task.WhenAny(handshake, Task.Delay(TimeSpan.FromSeconds(40)));
|
||||
await ThreadUtility.SwitchToMainThread();
|
||||
var handshakeResults = await handshake;
|
||||
var handshakeOk = handshakeResults.HasFlag(ServerHandshake.Result.Verified);
|
||||
if (!handshakeOk) {
|
||||
Log.Debug("ShowRetryPrompt because handshake result is {0}", handshakeResults);
|
||||
Prompts.ShowRetryDialog(serverInfo, handshakeResults, auto);
|
||||
// cancel trying to connect. They can use the retry button
|
||||
PlayerCodePatcher.UpdateHost(null).Forget();
|
||||
}
|
||||
|
||||
Log.Info($"Server is healthy after first handshake? {handshakeOk}");
|
||||
}
|
||||
|
||||
/// on Android, streaming assets are inside apk zip, which can only be read using unity web request
|
||||
private static async Task<BuildInfo> GetBuildInfo() {
|
||||
var path = BuildInfo.GetStoredPath();
|
||||
#if MOBILE_ANDROID
|
||||
var json = await RequestHelper.GetAsync(path);
|
||||
return await Task.Run(() => BuildInfo.FromJson(json));
|
||||
#else
|
||||
return await Task.Run(() => {
|
||||
return BuildInfo.FromJson(File.ReadAllText(path));
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
public static bool IsPlayer() => !Application.isEditor;
|
||||
|
||||
public static bool IsPlayerWithHotReload() {
|
||||
bool _;
|
||||
return IsPlayerWithHotReload(out _);
|
||||
}
|
||||
|
||||
public static bool IsPlayerWithHotReload(out bool onlyPrefabMissing) {
|
||||
onlyPrefabMissing = false;
|
||||
if (!IsPlayer() || !RuntimeSupportsHotReload || !HotReloadSettingsObject.I.IncludeInBuild) {
|
||||
return false;
|
||||
}
|
||||
onlyPrefabMissing = !HotReloadSettingsObject.I.PromptsPrefab;
|
||||
return !onlyPrefabMissing;
|
||||
}
|
||||
|
||||
public static bool RuntimeSupportsHotReload {
|
||||
get {
|
||||
#if DEVELOPMENT_BUILD && ENABLE_MONO
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 79392e60063749df984beec2908685bf
|
||||
timeCreated: 1674215564
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 254358
|
||||
packageName: Hot Reload | Edit Code Without Compiling
|
||||
packageVersion: 1.13.7
|
||||
assetPath: Packages/com.singularitygroup.hotreload/Runtime/PlayerEntrypoint.cs
|
||||
uploadId: 752503
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: da163599826f56e4f929d76e8e35c7e2
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,17 @@
|
||||
namespace SingularityGroup.HotReload {
|
||||
/// <summary>
|
||||
/// Utility class to set the log level of the Hot Reload package
|
||||
/// </summary>
|
||||
public static class HotReloadLogging {
|
||||
/// <summary>
|
||||
/// Sets the log level for logs inside the Hot Reload package
|
||||
/// The default log level is <see cref="LogLevel.Info"/>
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// To see more detailed logs, set the log level to <see cref="LogLevel.Debug"/>
|
||||
/// </remarks>
|
||||
public static void SetLogLevel(LogLevel level) {
|
||||
Log.minLevel = level;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 48845e010a33e2d4993888d9c18e8d95
|
||||
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.7
|
||||
assetPath: Packages/com.singularitygroup.hotreload/Runtime/Public/HotReloadLogging.cs
|
||||
uploadId: 752503
|
||||
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
namespace SingularityGroup.HotReload {
|
||||
/// <summary>
|
||||
/// Methods with this attribute will get invoked after a hot reload
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The method with this attribute needs to have no parameters.
|
||||
/// Further more it needs to either be static or and instance method inside a <see cref="UnityEngine.MonoBehaviour"/>.
|
||||
/// For the latter case the method of all instances of the <see cref="UnityEngine.MonoBehaviour"/> will be called.
|
||||
/// In case the method has a return value it will be ignored.
|
||||
/// </remarks>
|
||||
[AttributeUsage(AttributeTargets.Method)]
|
||||
public class InvokeOnHotReload : Attribute {
|
||||
}
|
||||
|
||||
public class MethodPatch {
|
||||
// Compiled by the Unity Editor. Null for newly added methods.
|
||||
public MethodBase originalMethod;
|
||||
// Method before Hot Reload applied it's patch. Null for newly added methods.
|
||||
public MethodBase previousMethod;
|
||||
// Method generated by Hot Reload.
|
||||
public MethodBase newMethod;
|
||||
|
||||
public MethodPatch(MethodBase originalMethod, MethodBase previousMethod, MethodBase newMethod) {
|
||||
this.originalMethod = originalMethod;
|
||||
this.previousMethod = previousMethod;
|
||||
this.newMethod = newMethod;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: de8fb6e6e05ccae46a410932e1545848
|
||||
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.7
|
||||
assetPath: Packages/com.singularitygroup.hotreload/Runtime/Public/InvokeOnHotReload.cs
|
||||
uploadId: 752503
|
||||
@@ -0,0 +1,22 @@
|
||||
using System;
|
||||
|
||||
namespace SingularityGroup.HotReload {
|
||||
/// <summary>
|
||||
/// Method with this attribute will get invoked when it gets patched
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The method with this attribute needs to have no parameters.
|
||||
/// Furthermore it needs to either be static or an instance method inside a <see cref="UnityEngine.MonoBehaviour"/>.
|
||||
/// For the latter case the method of all instances of the <see cref="UnityEngine.MonoBehaviour"/> will be called.
|
||||
/// In case the method has a return value it will be ignored.
|
||||
/// </remarks>
|
||||
[AttributeUsage(AttributeTargets.Method)]
|
||||
public class InvokeOnHotReloadLocal : Attribute {
|
||||
public readonly string methodToInvoke;
|
||||
|
||||
public InvokeOnHotReloadLocal(string methodToInvoke = null) {
|
||||
this.methodToInvoke = methodToInvoke;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f4629f348dad47d4add28eca71be748c
|
||||
timeCreated: 1694274524
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 254358
|
||||
packageName: Hot Reload | Edit Code Without Compiling
|
||||
packageVersion: 1.13.7
|
||||
assetPath: Packages/com.singularitygroup.hotreload/Runtime/Public/InvokeOnHotReloadLocal.cs
|
||||
uploadId: 752503
|
||||
108
Packages/com.singularitygroup.hotreload/Runtime/Public/Log.cs
Normal file
108
Packages/com.singularitygroup.hotreload/Runtime/Public/Log.cs
Normal file
@@ -0,0 +1,108 @@
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using JetBrains.Annotations;
|
||||
#if (UNITY_2019_4_OR_NEWER)
|
||||
using UnityEngine;
|
||||
#endif
|
||||
|
||||
namespace SingularityGroup.HotReload {
|
||||
public static class Log {
|
||||
[SuppressMessage("ReSharper", "FieldCanBeMadeReadOnly.Global")]
|
||||
public static LogLevel minLevel = LogLevel.Info;
|
||||
|
||||
/// <summary>
|
||||
/// Tag every log so that users know which logs came from Hot Reload
|
||||
/// </summary>
|
||||
private const string TAG = "[HotReload] ";
|
||||
|
||||
public static void Debug(string message) {
|
||||
if (minLevel <= LogLevel.Debug) {
|
||||
#if (UNITY_2019_4_OR_NEWER)
|
||||
UnityEngine.Debug.LogFormat(LogType.Log, LogOption.NoStacktrace, null, "{0}{1}", TAG, message);
|
||||
#else
|
||||
UnityEngine.Debug.Log(TAG + message);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
[StringFormatMethod("message")]
|
||||
public static void Debug(string message, params object[] args) {
|
||||
if (minLevel <= LogLevel.Debug) {
|
||||
#if (UNITY_2019_4_OR_NEWER)
|
||||
UnityEngine.Debug.LogFormat(LogType.Log, LogOption.NoStacktrace, null, TAG + message, args);
|
||||
#else
|
||||
UnityEngine.Debug.LogFormat(TAG + message, args);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
public static void Info(string message) {
|
||||
if (minLevel <= LogLevel.Info) {
|
||||
#if (UNITY_2019_4_OR_NEWER)
|
||||
UnityEngine.Debug.LogFormat(LogType.Log, LogOption.NoStacktrace, null, "{0}{1}", TAG, message);
|
||||
#else
|
||||
UnityEngine.Debug.Log(TAG + message);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
[StringFormatMethod("message")]
|
||||
public static void Info(string message, params object[] args) {
|
||||
if (minLevel <= LogLevel.Info) {
|
||||
#if (UNITY_2019_4_OR_NEWER)
|
||||
UnityEngine.Debug.LogFormat(LogType.Log, LogOption.NoStacktrace, null, TAG + message, args);
|
||||
#else
|
||||
UnityEngine.Debug.LogFormat(TAG + message, args);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
public static void Warning(string message) {
|
||||
if (minLevel <= LogLevel.Warning) {
|
||||
#if (UNITY_2019_4_OR_NEWER)
|
||||
UnityEngine.Debug.LogFormat(LogType.Warning, LogOption.NoStacktrace, null, "{0}{1}", TAG, message);
|
||||
#else
|
||||
UnityEngine.Debug.LogWarning(TAG + message);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
[StringFormatMethod("message")]
|
||||
public static void Warning(string message, params object[] args) {
|
||||
if (minLevel <= LogLevel.Warning) {
|
||||
#if (UNITY_2019_4_OR_NEWER)
|
||||
UnityEngine.Debug.LogFormat(LogType.Warning, LogOption.NoStacktrace, null, TAG + message, args);
|
||||
#else
|
||||
UnityEngine.Debug.LogWarningFormat(TAG + message, args);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
public static void Error(string message) {
|
||||
if (minLevel <= LogLevel.Error) {
|
||||
#if (UNITY_2019_4_OR_NEWER)
|
||||
UnityEngine.Debug.LogFormat(LogType.Error, LogOption.NoStacktrace, null, "{0}{1}", TAG, message);
|
||||
#else
|
||||
UnityEngine.Debug.LogError(TAG + message);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
[StringFormatMethod("message")]
|
||||
public static void Error(string message, params object[] args) {
|
||||
if (minLevel <= LogLevel.Error) {
|
||||
#if (UNITY_2019_4_OR_NEWER)
|
||||
UnityEngine.Debug.LogFormat(LogType.Error, LogOption.NoStacktrace, null, TAG + message, args);
|
||||
#else
|
||||
UnityEngine.Debug.LogErrorFormat(TAG + message, args);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
public static void Exception(Exception exception) {
|
||||
if (minLevel <= LogLevel.Exception) {
|
||||
UnityEngine.Debug.LogException(exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0719c57eeba24f4cac8d99016ba5b967
|
||||
timeCreated: 1674203567
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 254358
|
||||
packageName: Hot Reload | Edit Code Without Compiling
|
||||
packageVersion: 1.13.7
|
||||
assetPath: Packages/com.singularitygroup.hotreload/Runtime/Public/Log.cs
|
||||
uploadId: 752503
|
||||
@@ -0,0 +1,25 @@
|
||||
namespace SingularityGroup.HotReload {
|
||||
/// <summary>
|
||||
/// The log level enumeration for the Hot Reload package
|
||||
/// Used in <see cref="HotReloadLogging.SetLogLevel"/> to set the log level.
|
||||
/// </summary>
|
||||
public enum LogLevel {
|
||||
/// Debug logs are useful for developers of Hot Reload
|
||||
Debug = 1,
|
||||
|
||||
/// Info logs potentially useful for users of Hot Reload
|
||||
Info = 2,
|
||||
|
||||
/// Warnings are visible to users of Hot Reload
|
||||
Warning = 3,
|
||||
|
||||
/// Errors are visible to users of Hot Reload
|
||||
Error = 4,
|
||||
|
||||
/// Exceptions are visible to users of Hot Reload
|
||||
Exception = 5,
|
||||
|
||||
/// No logs are visible to users of Hot Reload
|
||||
Disabled = 6,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7b5723ec3c2140041a9c25bbf16eeac4
|
||||
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.7
|
||||
assetPath: Packages/com.singularitygroup.hotreload/Runtime/Public/LogLevel.cs
|
||||
uploadId: 752503
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user