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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,19 +1,4 @@
// <auto-generated/>
#pragma warning disable
#nullable enable annotations

// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace System.Runtime.CompilerServices
{
/// <summary>
/// Reserved to be used by the compiler for tracking metadata.
/// This class should not be used by developers in source code.
/// </summary>
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
internal static class IsExternalInit
{
}
}
[assembly: global::System.Runtime.CompilerServices.TypeForwardedTo(typeof(global::System.Runtime.CompilerServices.IsExternalInit))]
Original file line number Diff line number Diff line change
@@ -1,20 +1,4 @@
// <auto-generated/>
#pragma warning disable
#nullable enable annotations

// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace System.Runtime.CompilerServices
{
/// <summary>
/// Reserved for use by a compiler for tracking metadata.
/// This attribute should not be used by developers in source code.
/// </summary>
[global::System.AttributeUsage(global::System.AttributeTargets.Parameter, Inherited = false)]
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
internal sealed class RequiresLocationAttribute : global::System.Attribute
{
}
}
[assembly: global::System.Runtime.CompilerServices.TypeForwardedTo(typeof(global::System.Runtime.CompilerServices.RequiresLocationAttribute))]
18 changes: 15 additions & 3 deletions MonkeyLoader/Meta/ModLoadingLocation.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using EnumerableToolkit;
using Newtonsoft.Json;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
Expand All @@ -24,6 +25,8 @@ namespace MonkeyLoader.Meta
[JsonObject(MemberSerialization = MemberSerialization.OptIn)]
public sealed class ModLoadingLocation : IDisposable
{
private static readonly ConcurrentDictionary<string, DateTime> _lastChangeByFile = new(MonkeyLoader.FilesystemComparer);

private bool _disposedValue;
private Regex[] _ignorePatterns;
private FileSystemWatcher? _watcher;
Expand Down Expand Up @@ -97,7 +100,7 @@ internal bool ShouldWatcherBeActive
{
EnableRaisingEvents = true,
IncludeSubdirectories = Recursive,
NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.Size,
NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName
};

_watcher.Created += OnLoadMod;
Expand Down Expand Up @@ -188,17 +191,26 @@ private void Dispose(bool disposing)
private void OnLoadMod(object sender, FileSystemEventArgs e)
{
if (PassesIgnorePatterns(e.FullPath))
LoadMod?.Invoke(this, e.FullPath);
LoadMod?.Invoke(this, System.IO.Path.GetFullPath(e.FullPath));
}

private void OnReloadMod(object sender, FileSystemEventArgs e)
{
// use this in load / unload too
// FileName works for add / delete, but only write time works for in place modification
// use timeout to use last state from multiple changes that get written in chunks?
var fullPath = System.IO.Path.GetFullPath(e.FullPath);
if (_lastChangeByFile.TryGetValue(fullPath, out var lastChange) && (DateTime.UtcNow - lastChange).TotalSeconds < 5)
return;

OnUnloadMod(sender, e);
OnLoadMod(sender, e);

_lastChangeByFile[fullPath] = DateTime.UtcNow;
}

private void OnUnloadMod(object sender, FileSystemEventArgs e)
=> UnloadMod?.Invoke(this, e.FullPath);
=> UnloadMod?.Invoke(this, System.IO.Path.GetFullPath(e.FullPath));

/// <summary>
/// Called when a mod should be loaded because its got added or changed.
Expand Down
41 changes: 35 additions & 6 deletions MonkeyLoader/MonkeyLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,24 @@ public sealed class MonkeyLoader : IConfigOwner, IShutdown, IDisplayable, IIdent
/// </summary>
private readonly SortedSet<Mod> _allMods = new(Mod.AscendingComparer);

/// <summary>
/// Gets the <see cref="StringComparison"/> mode used by the current
/// <see cref="RuntimeInformation.IsOSPlatform(OSPlatform)">OS Platform</see>'s filesystem.
/// </summary>
public static StringComparison FilesystemComparison { get; }
= RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
? StringComparison.OrdinalIgnoreCase
: StringComparison.Ordinal;

/// <summary>
/// Gets the <see cref="StringComparer"/> used by the current
/// <see cref="RuntimeInformation.IsOSPlatform(OSPlatform)">OS Platform</see>'s filesystem.
/// </summary>
public static StringComparer FilesystemComparer { get; }
= RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
? StringComparer.OrdinalIgnoreCase
: StringComparer.Ordinal;

private ExecutionPhase _phase;

/// <summary>
Expand Down Expand Up @@ -193,16 +211,16 @@ private set
internal AssemblyPool GameAssemblyPool { get; }
internal AssemblyPool PatcherAssemblyPool { get; }
internal AssemblyPool RuntimeAssemblyPool { get; }

public IAssemblyLoadStrategy AssemblyLoadStrategy { get; }

public Assembly? ResolveAssemblyFromPoolsAndMods(System.Reflection.AssemblyName assemblyName)
{
var mlAssemblyName = new AssemblyName(assemblyName.FullName);

if (PatcherAssemblyPool.TryResolveAssembly(mlAssemblyName, out var assembly))
return assembly;

if (GameAssemblyPool.TryResolveAssembly(mlAssemblyName, out assembly))
return assembly;

Expand Down Expand Up @@ -268,7 +286,7 @@ public MonkeyLoader(LoggingController loggingController, string configPath = Def
#if NET5_0_OR_GREATER
AssemblyLoadStrategy = new AssemblyLoadContextLoadStrategy();
#endif

ConfigPath = configPath;
Id = GetId(configPath);

Expand All @@ -284,9 +302,20 @@ public MonkeyLoader(LoggingController loggingController, string configPath = Def

foreach (var modLocation in Locations.Mods)
{
modLocation.LoadMod += (mL, path) => TryLoadAndRunMod(path, out _);
modLocation.LoadMod += (mL, path) =>
{
var stackTrace = Environment.StackTrace;
Logger.Info(() => $"Trying to hot-load mod from: {path}");
Logger.Info(() => stackTrace);
TryLoadAndRunMod(path, out _);
};

modLocation.UnloadMod += (mL, path) =>
{
var stackTrace = Environment.StackTrace;
Logger.Info(() => $"Trying to unload mod from: {path}");
Logger.Info(() => stackTrace);

if (TryFindModByLocation(path, out var mod))
ShutdownMod(mod);
};
Expand Down Expand Up @@ -1037,7 +1066,7 @@ public bool TryFindModByLocation(string location, [NotNullWhen(true)] out Mod? m
return false;
}

var mods = _allMods.Where(mod => location.Equals(mod.Location, StringComparison.Ordinal)).ToArray();
var mods = _allMods.Where(mod => location.Equals(mod.Location, FilesystemComparison)).ToArray();

if (mods.Length == 0)
return false;
Expand Down