[Milestone] AssemblyLoader completed.
Details: - Assembly Mgmt Service for loading now a separate interface, not intended for normal use. - Assembly Loader work; implemented custom dictionary key and table. - Assembly loading work. - EventService completed. - Moved assembly extensions to ModUtils.cs - Work to event service. NetworkService work - Added ImpromptuInterfaces package. - Networking Service work to support NetVars - Event Service - Added assemblies references package for script compilation. Updated Roslyn version for compatibility. - Package Loading work. Swap Harmony to HarmonyX - More refactor conversion to FluentResults. - Updated StylesService to return Results. - Refactor of PackageService partially complete. - Made IService.Reset() required to return a Result. - Moved plugin/assembly related code to their own folder (same namespace). - Updated interfaces to reflect the use of Result<T>. - Partial refactor, incomplete. - Added 'FluentResults' so we can stop using cursed Exception-based flow control in loading code. - Added 'OneOf' nuget package: https://github.com/mcintyre321/OneOf for the implementation of the Optional<T> pattern and complex discrete return types instead of cursed enums (see current AssemblyManager.cs). - Reapplied old branch changes.
This commit is contained in:
@@ -2,15 +2,17 @@ using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Barotrauma.LuaCs.Configuration;
|
||||
|
||||
public class DisplayableData : IDisplayableData
|
||||
public record DisplayableData : IDisplayableData
|
||||
{
|
||||
public string Name { get; private set; }
|
||||
public string ModName { get; private set; }
|
||||
public string DisplayName { get; private set; }
|
||||
public string DisplayModName { get; private set; }
|
||||
public string DisplayCategory { get; private set; }
|
||||
public string Tooltip { get; private set; }
|
||||
public string ImageIcon { get; private set; }
|
||||
public Point IconResolution { get; private set; }
|
||||
public bool ShowWhenNotLoaded { get; private set; }
|
||||
public string InternalName { get; init; }
|
||||
public ContentPackage OwnerPackage { get; init; }
|
||||
public string FallbackPackageName { get; init; }
|
||||
public string DisplayName { get; init; }
|
||||
public string DisplayModName { get; init; }
|
||||
public string DisplayCategory { get; init; }
|
||||
public string Tooltip { get; init; }
|
||||
public string ImageIcon { get; init; }
|
||||
public Point IconResolution { get; init; }
|
||||
public bool ShowWhenNotLoaded { get; init; }
|
||||
public string Description { get; init; }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
using System;
|
||||
|
||||
namespace Barotrauma.LuaCs.Configuration;
|
||||
|
||||
public interface IConfigControl : IConfigBase
|
||||
{
|
||||
event Action<IConfigControl> OnDown;
|
||||
KeyOrMouse Value { get; }
|
||||
bool IsAssignable(KeyOrMouse value);
|
||||
bool TrySetValue(KeyOrMouse value);
|
||||
bool IsDown();
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Numerics;
|
||||
using Barotrauma.LuaCs.Data;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Barotrauma.LuaCs.Configuration;
|
||||
@@ -6,16 +7,8 @@ namespace Barotrauma.LuaCs.Configuration;
|
||||
/// <summary>
|
||||
/// Contains the Display Data for use with Menus.
|
||||
/// </summary>
|
||||
public interface IDisplayableData
|
||||
public interface IDisplayableData : IDataInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Internal name of the instance.
|
||||
/// </summary>
|
||||
string Name { get; }
|
||||
/// <summary>
|
||||
/// Internal mod name of the instance. ContentPackage name will be used by default.
|
||||
/// </summary>
|
||||
string ModName { get; }
|
||||
/// <summary>
|
||||
/// The name to display in GUIs and Menus.
|
||||
/// </summary>
|
||||
@@ -44,6 +37,10 @@ public interface IDisplayableData
|
||||
/// Whether to show the entry in the menu when not loaded.
|
||||
/// </summary>
|
||||
bool ShowWhenNotLoaded { get; }
|
||||
/// <summary>
|
||||
/// What does this setting do?
|
||||
/// </summary>
|
||||
string Description { get; }
|
||||
}
|
||||
|
||||
public interface IDisplayableInitialize
|
||||
@@ -53,8 +50,8 @@ public interface IDisplayableInitialize
|
||||
// copy this as needed
|
||||
/*public void Initialize(IDisplayableData values)
|
||||
{
|
||||
this.Name = values.Name;
|
||||
this.ModName = values.ModName;
|
||||
this.InternalName = values.InternalName;
|
||||
this.OwnerPackage = values.OwnerPackage;
|
||||
this.DisplayName = values.DisplayName;
|
||||
this.DisplayModName = values.DisplayModName;
|
||||
this.DisplayCategory = values.DisplayCategory;
|
||||
@@ -17,5 +17,6 @@ public record StylesResourceInfo : IStylesResourceInfo
|
||||
public bool Optional { get; init; }
|
||||
public ImmutableArray<CultureInfo> SupportedCultures { get; init; }
|
||||
public string InternalName { get; init; }
|
||||
public ContentPackage OwnerPackage { get; init; }
|
||||
public ImmutableArray<IPackageDependencyInfo> Dependencies { get; init; }
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ namespace Barotrauma.LuaCs.Data;
|
||||
|
||||
public partial interface IModConfigInfo : IStylesResourcesInfo { }
|
||||
|
||||
public interface IStylesResourceInfo : IResourceInfo, IResourceCultureInfo, ILoadableResourceInfo, IPackageDependenciesInfo { }
|
||||
public interface IStylesResourceInfo : IResourceInfo, IResourceCultureInfo, IDataInfo, IPackageDependenciesInfo { }
|
||||
|
||||
public interface IStylesResourcesInfo
|
||||
{
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
using Barotrauma.Networking;
|
||||
|
||||
namespace Barotrauma.LuaCs.Services.Compatibility;
|
||||
|
||||
internal partial interface ILuaCsNetworking : ILuaCsShim
|
||||
{
|
||||
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
namespace Barotrauma.LuaCs.Services;
|
||||
|
||||
public interface IClientLoggerService : IService
|
||||
public interface IClientLoggerService : IReusableService
|
||||
{
|
||||
void AddToGUIUpdateList();
|
||||
void ShowErrorOverlay(string message, float time = 5f, float duration = 1.5f);
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Barotrauma.LuaCs.Configuration;
|
||||
using Barotrauma.LuaCs.Networking;
|
||||
using Barotrauma.Networking;
|
||||
|
||||
namespace Barotrauma.LuaCs.Services;
|
||||
|
||||
public partial interface IConfigService
|
||||
{
|
||||
/*
|
||||
* Immediate mode
|
||||
*/
|
||||
FluentResults.Result<IConfigEntry<T>> AddConfigEntry<T>(IDisplayableData data,
|
||||
T defaultValue,
|
||||
NetSync syncMode = NetSync.None,
|
||||
ClientPermissions permissions = ClientPermissions.None,
|
||||
Func<T, bool> valueChangePredicate = null,
|
||||
Action<IConfigEntry<T>> onValueChanged = null) where T : IConvertible, IEquatable<T>;
|
||||
|
||||
FluentResults.Result<IConfigList> AddConfigList(IDisplayableData data,
|
||||
int defaultIndex, IReadOnlyList<string> values,
|
||||
NetSync syncMode = NetSync.None,
|
||||
ClientPermissions permissions = ClientPermissions.None,
|
||||
Func<IConfigList, int, bool> valueChangePredicate = null,
|
||||
Action<IConfigList, int> onValueChanged = null);
|
||||
|
||||
FluentResults.Result<IConfigRangeEntry<T>> AddConfigRangeEntry<T>(IDisplayableData data,
|
||||
T defaultValue, T minValue, T maxValue,
|
||||
Func<IConfigRangeEntry<T>, int> getStepCount,
|
||||
NetSync syncMode = NetSync.None,
|
||||
ClientPermissions permissions = ClientPermissions.None,
|
||||
Func<T, bool> valueChangePredicate = null,
|
||||
Action<IConfigEntry<T>> onValueChanged = null) where T : IConvertible, IEquatable<T>;
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
/// <summary>
|
||||
/// Loads XML Style assets from the given content package.
|
||||
/// </summary>
|
||||
public interface IStylesService : IService
|
||||
public interface IStylesService : IReusableService
|
||||
{
|
||||
/// <summary>
|
||||
/// Tries to load the styles file for the given contentpackage and path into a new UIStylesProcessor instance.
|
||||
@@ -12,11 +12,11 @@ public interface IStylesService : IService
|
||||
/// <param name="package"></param>
|
||||
/// <param name="path"></param>
|
||||
/// <returns></returns>
|
||||
bool TryLoadStylesFile(ContentPackage package, ContentPath path);
|
||||
FluentResults.Result LoadStylesFile(ContentPackage package, ContentPath path);
|
||||
/// <summary>
|
||||
/// Unloads all styles assets and UIStyleProcessor instances.
|
||||
/// </summary>
|
||||
void UnloadAllStyles();
|
||||
FluentResults.Result UnloadAllStyles();
|
||||
|
||||
/// <summary>
|
||||
/// Tries to the get the font asset by xml asset name, returns null on failure.
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
using Barotrauma.LuaCs.Services;
|
||||
using Barotrauma.Networking;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Barotrauma.LuaCs.Networking;
|
||||
|
||||
partial class NetworkingService
|
||||
{
|
||||
private Dictionary<ushort, Queue<IReadMessage>> receiveQueue = new Dictionary<ushort, Queue<IReadMessage>>();
|
||||
|
||||
public void SendSyncMessage()
|
||||
{
|
||||
if (GameMain.Client == null) { return; }
|
||||
|
||||
WriteOnlyMessage message = new WriteOnlyMessage();
|
||||
message.WriteByte((byte)ClientPacketHeader.LUA_NET_MESSAGE);
|
||||
message.WriteByte((byte)LuaCsClientToServer.RequestAllIds);
|
||||
GameMain.Client.ClientPeer.Send(message, DeliveryMethod.Reliable);
|
||||
}
|
||||
|
||||
public void NetMessageReceived(IReadMessage netMessage, ServerPacketHeader header, Client client = null)
|
||||
{
|
||||
if (header != ServerPacketHeader.LUA_NET_MESSAGE)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
LuaCsServerToClient luaCsHeader = (LuaCsServerToClient)netMessage.ReadByte();
|
||||
|
||||
switch (luaCsHeader)
|
||||
{
|
||||
case LuaCsServerToClient.NetMessageString:
|
||||
HandleNetMessageString(netMessage);
|
||||
break;
|
||||
|
||||
case LuaCsServerToClient.NetMessageId:
|
||||
HandleNetMessageId(netMessage);
|
||||
break;
|
||||
|
||||
case LuaCsServerToClient.ReceiveIds:
|
||||
ReadIds(netMessage);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public INetWriteMessage Start(Guid netId)
|
||||
{
|
||||
var message = new WriteOnlyMessage();
|
||||
|
||||
message.WriteByte((byte)ClientPacketHeader.LUA_NET_MESSAGE);
|
||||
|
||||
if (idToPacket.ContainsKey(netId))
|
||||
{
|
||||
message.WriteByte((byte)LuaCsClientToServer.NetMessageId);
|
||||
message.WriteUInt16(idToPacket[netId]);
|
||||
}
|
||||
else
|
||||
{
|
||||
message.WriteByte((byte)LuaCsClientToServer.NetMessageString);
|
||||
message.WriteBytes(netId.ToByteArray(), 0, 16);
|
||||
}
|
||||
|
||||
return message.ToNetWriteMessage();
|
||||
}
|
||||
|
||||
public void RequestId(Guid netId)
|
||||
{
|
||||
if (idToPacket.ContainsKey(netId)) { return; }
|
||||
|
||||
if (GameMain.Client == null) { return; }
|
||||
|
||||
WriteOnlyMessage message = new WriteOnlyMessage();
|
||||
message.WriteByte((byte)ClientPacketHeader.LUA_NET_MESSAGE);
|
||||
message.WriteByte((byte)LuaCsClientToServer.RequestSingleId);
|
||||
|
||||
message.WriteBytes(netId.ToByteArray(), 0, 16);
|
||||
|
||||
Send(message, DeliveryMethod.Reliable);
|
||||
}
|
||||
|
||||
public void Send(IWriteMessage netMessage, DeliveryMethod deliveryMethod = DeliveryMethod.Reliable)
|
||||
{
|
||||
GameMain.Client.ClientPeer.Send(netMessage, deliveryMethod);
|
||||
}
|
||||
|
||||
private void HandleNetMessageId(IReadMessage netMessage, Client client = null)
|
||||
{
|
||||
ushort id = netMessage.ReadUInt16();
|
||||
|
||||
if (packetToId.ContainsKey(id))
|
||||
{
|
||||
HandleNetMessage(netMessage, packetToId[id], client);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!receiveQueue.ContainsKey(id)) { receiveQueue[id] = new Queue<IReadMessage>(); }
|
||||
receiveQueue[id].Enqueue(netMessage);
|
||||
|
||||
if (GameSettings.CurrentConfig.VerboseLogging)
|
||||
{
|
||||
LuaCsLogger.LogMessage($"Received NetMessage with unknown id {id} from server, storing in queue in case we receive the id later.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ReadIds(IReadMessage netMessage)
|
||||
{
|
||||
ushort size = netMessage.ReadUInt16();
|
||||
|
||||
for (int i = 0; i < size; i++)
|
||||
{
|
||||
ushort packetId = netMessage.ReadUInt16();
|
||||
Guid netId = new Guid(netMessage.ReadBytes(16));
|
||||
|
||||
packetToId[packetId] = netId;
|
||||
idToPacket[netId] = packetId;
|
||||
|
||||
if (!receiveQueue.ContainsKey(packetId))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// We could have received messages before receiving the sync message, so we need to process them now
|
||||
|
||||
while (receiveQueue[packetId].TryDequeue(out var queueMessage))
|
||||
{
|
||||
if (netReceives.ContainsKey(netId))
|
||||
{
|
||||
netReceives[netId](queueMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,10 +9,10 @@ namespace Barotrauma.LuaCs.Services;
|
||||
public partial class PackageService : IStylesResourcesInfo
|
||||
{
|
||||
private readonly Lazy<IStylesService> _stylesService;
|
||||
public IStylesService Styles => _stylesService.Value;
|
||||
|
||||
public PackageService(
|
||||
Lazy<IXmlModConfigConverterService> converterService,
|
||||
Lazy<ILegacyConfigService> legacyConfigService,
|
||||
Lazy<IModConfigParserService> configParserService,
|
||||
Lazy<ILuaScriptService> luaScriptService,
|
||||
Lazy<ILocalizationService> localizationService,
|
||||
Lazy<IPluginService> pluginService,
|
||||
@@ -22,8 +22,7 @@ public partial class PackageService : IStylesResourcesInfo
|
||||
IStorageService storageService,
|
||||
ILoggerService loggerService)
|
||||
{
|
||||
_modConfigConverterService = converterService;
|
||||
_legacyConfigService = legacyConfigService;
|
||||
_configParserService = configParserService;
|
||||
_luaScriptService = luaScriptService;
|
||||
_localizationService = localizationService;
|
||||
_pluginService = pluginService;
|
||||
@@ -36,7 +35,7 @@ public partial class PackageService : IStylesResourcesInfo
|
||||
|
||||
public ImmutableArray<IStylesResourceInfo> StylesResourceInfos => ModConfigInfo?.StylesResourceInfos ?? ImmutableArray<IStylesResourceInfo>.Empty;
|
||||
|
||||
public void LoadStyles([NotNull]IStylesResourcesInfo stylesInfo)
|
||||
public FluentResults.Result LoadStyles([NotNull]IStylesResourcesInfo stylesInfo)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using FluentResults;
|
||||
using FluentResults.LuaCs;
|
||||
|
||||
namespace Barotrauma.LuaCs.Services;
|
||||
|
||||
@@ -18,44 +20,53 @@ public class StylesService : IStylesService
|
||||
_loggerService = loggerService;
|
||||
}
|
||||
|
||||
public bool TryLoadStylesFile(ContentPackage package, ContentPath path)
|
||||
public FluentResults.Result LoadStylesFile(ContentPackage package, ContentPath path)
|
||||
{
|
||||
//check if file already in dict
|
||||
if (_loadedProcessors.ContainsKey(path.FullPath))
|
||||
{
|
||||
return true;
|
||||
return FluentResults.Result.Ok();
|
||||
}
|
||||
//check if file exists
|
||||
if (_storageService.FileExists(path.FullPath))
|
||||
if (_storageService.FileExists(path.FullPath) is {} result
|
||||
&& result.IsFailed | (result.IsSuccess & result.Value == false))
|
||||
{
|
||||
try
|
||||
{
|
||||
var styleProcessor = new UIStyleProcessor(package, path);
|
||||
styleProcessor.LoadFile();
|
||||
_loadedProcessors.Add(path.FullPath, styleProcessor);
|
||||
}
|
||||
catch (InvalidDataException exception)
|
||||
{
|
||||
_loggerService.LogError($"XmlAssetService.TryLoadStylesFile failed for ContentPackage {package.Name}: Exception: {exception.Message}");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
return FluentResults.Result.Fail(result.Errors)
|
||||
.WithError(new Error($"{nameof(StylesService)}.{nameof(LoadStylesFile)} file does not exist!")
|
||||
.WithMetadata(MetadataType.ExceptionObject, this)
|
||||
.WithMetadata(MetadataType.RootObject, package));
|
||||
}
|
||||
|
||||
return false;
|
||||
try
|
||||
{
|
||||
var styleProcessor = new UIStyleProcessor(package, path);
|
||||
styleProcessor.LoadFile();
|
||||
_loadedProcessors.Add(path.FullPath, styleProcessor);
|
||||
}
|
||||
catch (InvalidDataException exception)
|
||||
{
|
||||
return FluentResults.Result.Fail(new Error($"{nameof(StylesService)}.{nameof(LoadStylesFile)} failed for ContentPackage {package.Name}: Exception: {exception.Message}")
|
||||
.WithMetadata(MetadataType.ExceptionDetails, exception.Message)
|
||||
.WithMetadata(MetadataType.ExceptionObject, this)
|
||||
.WithMetadata(MetadataType.RootObject, package)
|
||||
.WithMetadata(MetadataType.StackTrace, exception.StackTrace));
|
||||
}
|
||||
|
||||
return FluentResults.Result.Ok();
|
||||
}
|
||||
|
||||
public void UnloadAllStyles()
|
||||
public FluentResults.Result UnloadAllStyles()
|
||||
{
|
||||
if (NoProcessorsLoaded)
|
||||
return;
|
||||
return FluentResults.Result.Fail(new Error($"{nameof(StylesService)}.{nameof(UnloadAllStyles)}: No processors have been loaded.")
|
||||
.WithMetadata(MetadataType.ExceptionObject, this));
|
||||
|
||||
foreach (var processor in _loadedProcessors)
|
||||
{
|
||||
processor.Value.UnloadFile();
|
||||
}
|
||||
_loadedProcessors.Clear();
|
||||
return FluentResults.Result.Ok();
|
||||
}
|
||||
|
||||
public GUIFont GetFont(string fontName)
|
||||
@@ -131,8 +142,8 @@ public class StylesService : IStylesService
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
public FluentResults.Result Reset()
|
||||
{
|
||||
UnloadAllStyles();
|
||||
return UnloadAllStyles();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -603,7 +603,7 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
ServerPacketHeader header = (ServerPacketHeader)inc.ReadByte();
|
||||
|
||||
GameMain.LuaCs.Networking.NetMessageReceived(inc, header);
|
||||
GameMain.LuaCs.NetworkingService.NetMessageReceived(inc, header);
|
||||
|
||||
if (roundInitStatus == RoundInitStatus.WaitingForStartGameFinalize
|
||||
&& header is not (
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
<Configurations>Debug;Release;Unstable</Configurations>
|
||||
<InvariantGlobalization>true</InvariantGlobalization>
|
||||
<WarningsAsErrors>;NU1605;CS0114;CS0108;CS8597;CS8600;CS8601;CS8602;CS8603;CS8604;CS8605;CS8606;CS8607;CS8608;CS8609;CS8610;CS8611;CS8612;CS8613;CS8614;CS8615;CS8616;CS8617;CS8618;CS8619;CS8620;CS8621;CS8622;CS8624;CS8625;CS8626;CS8629;CS8631;CS8632;CS8633;CS8634;CS8638;CS8643;CS8644;CS8645;CS8653;CS8654;CS8655;CS8667;CS8669;CS8670;CS8714;CS8717;CS8765</WarningsAsErrors>
|
||||
<LangVersion>latest</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
<InvariantGlobalization>true</InvariantGlobalization>
|
||||
<WarningsAsErrors>;NU1605;CS0114;CS0108;CS8597;CS8600;CS8601;CS8602;CS8603;CS8604;CS8605;CS8606;CS8607;CS8608;CS8609;CS8610;CS8611;CS8612;CS8613;CS8614;CS8615;CS8616;CS8617;CS8618;CS8619;CS8620;CS8621;CS8622;CS8624;CS8625;CS8626;CS8629;CS8631;CS8632;CS8633;CS8634;CS8638;CS8643;CS8644;CS8645;CS8653;CS8654;CS8655;CS8667;CS8669;CS8670;CS8714;CS8717;CS8765</WarningsAsErrors>
|
||||
<ErrorOnDuplicatePublishOutputFiles>false</ErrorOnDuplicatePublishOutputFiles>
|
||||
<LangVersion>latest</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
<InvariantGlobalization>true</InvariantGlobalization>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
<WarningsAsErrors>;NU1605;CS0114;CS0108;CS8597;CS8600;CS8601;CS8602;CS8603;CS8604;CS8605;CS8606;CS8607;CS8608;CS8609;CS8610;CS8611;CS8612;CS8613;CS8614;CS8615;CS8616;CS8617;CS8618;CS8619;CS8620;CS8621;CS8622;CS8624;CS8625;CS8626;CS8629;CS8631;CS8632;CS8633;CS8634;CS8638;CS8643;CS8644;CS8645;CS8653;CS8654;CS8655;CS8667;CS8669;CS8670;CS8714;CS8717;CS8765</WarningsAsErrors>
|
||||
<LangVersion>latest</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
<Configurations>Debug;Release;Unstable</Configurations>
|
||||
<InvariantGlobalization>true</InvariantGlobalization>
|
||||
<WarningsAsErrors>;NU1605;CS0114;CS0108;CS8597;CS8600;CS8601;CS8602;CS8603;CS8604;CS8605;CS8606;CS8607;CS8608;CS8609;CS8610;CS8611;CS8612;CS8613;CS8614;CS8615;CS8616;CS8617;CS8618;CS8619;CS8620;CS8621;CS8622;CS8624;CS8625;CS8626;CS8629;CS8631;CS8632;CS8633;CS8634;CS8638;CS8643;CS8644;CS8645;CS8653;CS8654;CS8655;CS8667;CS8669;CS8670;CS8714;CS8717;CS8765</WarningsAsErrors>
|
||||
<LangVersion>latest</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
<Configurations>Debug;Release;Unstable</Configurations>
|
||||
<InvariantGlobalization>true</InvariantGlobalization>
|
||||
<WarningsAsErrors>;NU1605;CS0114;CS0108;CS8597;CS8600;CS8601;CS8602;CS8603;CS8604;CS8605;CS8606;CS8607;CS8608;CS8609;CS8610;CS8611;CS8612;CS8613;CS8614;CS8615;CS8616;CS8617;CS8618;CS8619;CS8620;CS8621;CS8622;CS8624;CS8625;CS8626;CS8629;CS8631;CS8632;CS8633;CS8634;CS8638;CS8643;CS8644;CS8645;CS8653;CS8654;CS8655;CS8667;CS8669;CS8670;CS8714;CS8717;CS8765</WarningsAsErrors>
|
||||
<LangVersion>latest</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
|
||||
@@ -368,7 +368,7 @@ namespace Barotrauma
|
||||
performanceCounterTimer.Stop();
|
||||
if (GameMain.LuaCs.PerformanceCounter.EnablePerformanceCounter)
|
||||
{
|
||||
GameMain.LuaCs.PerformanceCounter.UpdateElapsedTime = (double)performanceCounterTimer.ElapsedTicks / Stopwatch.Frequency;
|
||||
GameMain.LuaCs.PerformanceCounter.AddElapsedTicks(new SimplePerformanceData("Update", performanceCounterTimer.ElapsedTicks));
|
||||
}
|
||||
performanceCounterTimer.Reset();
|
||||
|
||||
@@ -452,7 +452,7 @@ namespace Barotrauma
|
||||
public void Exit()
|
||||
{
|
||||
ShouldRun = false;
|
||||
GameMain.LuaCs.Stop();
|
||||
GameMain.LuaCs.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,176 @@
|
||||
using Barotrauma.LuaCs.Services;
|
||||
using Barotrauma.Networking;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma.LuaCs.Networking;
|
||||
|
||||
partial class NetworkingService
|
||||
{
|
||||
private const int MaxRegisterPerClient = 1000;
|
||||
|
||||
private Dictionary<string, int> clientRegisterCount = new Dictionary<string, int>();
|
||||
|
||||
private ushort currentId = 0;
|
||||
|
||||
public INetWriteMessage Start(Guid netId)
|
||||
{
|
||||
var message = new WriteOnlyMessage();
|
||||
|
||||
message.WriteByte((byte)ServerPacketHeader.LUA_NET_MESSAGE);
|
||||
|
||||
if (idToPacket.ContainsKey(netId))
|
||||
{
|
||||
message.WriteByte((byte)LuaCsServerToClient.NetMessageId);
|
||||
message.WriteUInt16(idToPacket[netId]);
|
||||
}
|
||||
else
|
||||
{
|
||||
message.WriteByte((byte)LuaCsServerToClient.NetMessageString);
|
||||
message.WriteBytes(netId.ToByteArray(), 0, 16);
|
||||
}
|
||||
|
||||
return message.ToNetWriteMessage();
|
||||
}
|
||||
|
||||
public void NetMessageReceived(IReadMessage netMessage, ClientPacketHeader header, Client client = null)
|
||||
{
|
||||
if (header != ClientPacketHeader.LUA_NET_MESSAGE)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
LuaCsClientToServer luaCsHeader = (LuaCsClientToServer)netMessage.ReadByte();
|
||||
|
||||
switch (luaCsHeader)
|
||||
{
|
||||
case LuaCsClientToServer.NetMessageString:
|
||||
HandleNetMessageString(netMessage, client);
|
||||
break;
|
||||
|
||||
case LuaCsClientToServer.NetMessageId:
|
||||
HandleNetMessageId(netMessage, client);
|
||||
break;
|
||||
|
||||
case LuaCsClientToServer.RequestAllIds:
|
||||
WriteAllIds(client);
|
||||
break;
|
||||
|
||||
case LuaCsClientToServer.RequestSingleId:
|
||||
RequestIdSingle(netMessage, client);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleNetMessageId(IReadMessage netMessage, Client client = null)
|
||||
{
|
||||
ushort id = netMessage.ReadUInt16();
|
||||
|
||||
if (packetToId.ContainsKey(id))
|
||||
{
|
||||
Guid netId = packetToId[id];
|
||||
|
||||
HandleNetMessage(netMessage, netId, client);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (GameSettings.CurrentConfig.VerboseLogging)
|
||||
{
|
||||
LuaCsLogger.LogError($"Received NetMessage for unknown id {id} from {GameServer.ClientLogName(client)}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ushort RegisterId(Guid netId)
|
||||
{
|
||||
if (idToPacket.ContainsKey(netId))
|
||||
{
|
||||
return idToPacket[netId];
|
||||
}
|
||||
|
||||
if (currentId >= ushort.MaxValue)
|
||||
{
|
||||
LuaCsLogger.LogError($"Tried to register more than {ushort.MaxValue} network ids!");
|
||||
return 0;
|
||||
}
|
||||
|
||||
currentId++;
|
||||
|
||||
packetToId[currentId] = netId;
|
||||
idToPacket[netId] = currentId;
|
||||
|
||||
WriteIdToAll(currentId, netId);
|
||||
|
||||
return currentId;
|
||||
}
|
||||
|
||||
private void RequestIdSingle(IReadMessage netMessage, Client client)
|
||||
{
|
||||
Guid netId = new Guid(netMessage.ReadBytes(16));
|
||||
|
||||
if (!idToPacket.ContainsKey(netId) && client.AccountId.TryUnwrap(out AccountId id))
|
||||
{
|
||||
if (!clientRegisterCount.ContainsKey(id.StringRepresentation))
|
||||
{
|
||||
clientRegisterCount[id.StringRepresentation] = 0;
|
||||
}
|
||||
|
||||
clientRegisterCount[id.StringRepresentation]++;
|
||||
|
||||
if (clientRegisterCount[id.StringRepresentation] > MaxRegisterPerClient)
|
||||
{
|
||||
LuaCsLogger.Log($"{GameServer.ClientLogName(client)} Tried to register more than {MaxRegisterPerClient} Ids!");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
RegisterId(netId);
|
||||
}
|
||||
|
||||
private void WriteIdToAll(ushort packet, Guid netId)
|
||||
{
|
||||
WriteOnlyMessage message = new WriteOnlyMessage();
|
||||
message.WriteByte((byte)ServerPacketHeader.LUA_NET_MESSAGE);
|
||||
message.WriteByte((byte)LuaCsServerToClient.ReceiveIds);
|
||||
|
||||
message.WriteUInt16(1);
|
||||
message.WriteUInt16(packet);
|
||||
message.WriteBytes(netId.ToByteArray(), 0, 16);
|
||||
|
||||
Send(message, null, DeliveryMethod.Reliable);
|
||||
}
|
||||
|
||||
private void WriteAllIds(Client client)
|
||||
{
|
||||
WriteOnlyMessage message = new WriteOnlyMessage();
|
||||
message.WriteByte((byte)ServerPacketHeader.LUA_NET_MESSAGE);
|
||||
message.WriteByte((byte)LuaCsServerToClient.ReceiveIds);
|
||||
|
||||
message.WriteUInt16((ushort)packetToId.Count());
|
||||
foreach ((ushort packet, Guid netId) in packetToId)
|
||||
{
|
||||
message.WriteUInt16(packet);
|
||||
message.WriteBytes(netId.ToByteArray(), 0, 16);
|
||||
}
|
||||
|
||||
Send(message, client.Connection, DeliveryMethod.Reliable);
|
||||
}
|
||||
|
||||
public void ClientWriteLobby(Client client) => GameMain.Server.ClientWriteLobby(client);
|
||||
|
||||
public void Send(IWriteMessage netMessage, NetworkConnection connection = null, DeliveryMethod deliveryMethod = DeliveryMethod.Reliable)
|
||||
{
|
||||
if (connection == null)
|
||||
{
|
||||
foreach (NetworkConnection conn in Client.ClientList.Select(c => c.Connection))
|
||||
{
|
||||
GameMain.Server.ServerPeer.Send(netMessage, conn, deliveryMethod);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
GameMain.Server.ServerPeer.Send(netMessage, connection, deliveryMethod);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,8 +8,7 @@ namespace Barotrauma.LuaCs.Services;
|
||||
public partial class PackageService
|
||||
{
|
||||
public PackageService(
|
||||
Lazy<IXmlModConfigConverterService> converterService,
|
||||
Lazy<ILegacyConfigService> legacyConfigService,
|
||||
Lazy<IModConfigParserService> configParserService,
|
||||
Lazy<ILuaScriptService> luaScriptService,
|
||||
Lazy<ILocalizationService> localizationService,
|
||||
Lazy<IPluginService> pluginService,
|
||||
@@ -18,8 +17,7 @@ public partial class PackageService
|
||||
IStorageService storageService,
|
||||
ILoggerService loggerService)
|
||||
{
|
||||
_modConfigConverterService = converterService;
|
||||
_legacyConfigService = legacyConfigService;
|
||||
_configParserService = configParserService;
|
||||
_luaScriptService = luaScriptService;
|
||||
_localizationService = localizationService;
|
||||
_pluginService = pluginService;
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
<Configurations>Debug;Release;Unstable</Configurations>
|
||||
<InvariantGlobalization>true</InvariantGlobalization>
|
||||
<WarningsAsErrors>;NU1605;CS0114;CS0108;CS8597;CS8600;CS8601;CS8602;CS8603;CS8604;CS8605;CS8606;CS8607;CS8608;CS8609;CS8610;CS8611;CS8612;CS8613;CS8614;CS8615;CS8616;CS8617;CS8618;CS8619;CS8620;CS8621;CS8622;CS8624;CS8625;CS8626;CS8629;CS8631;CS8632;CS8633;CS8634;CS8638;CS8643;CS8644;CS8645;CS8653;CS8654;CS8655;CS8667;CS8669;CS8670;CS8714;CS8717;CS8765</WarningsAsErrors>
|
||||
<LangVersion>latest</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
<Project>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Luatrauma.Internal.AssemblyPublicizer.MSBuild" Version="0.1.4" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="4.1.0" />
|
||||
<PackageReference Include="MonoMod.RuntimeDetour" Version="25.2.3" />
|
||||
<PackageReference Include="HarmonyX" Version="2.14.0" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.11.0" />
|
||||
<PackageReference Include="HarmonyX" Version="2.13.0" />
|
||||
<PackageReference Include="Sigil" Version="5.0.0" />
|
||||
<PackageReference Include="LightInject" Version="6.6.4" />
|
||||
<PackageReference Include="QuikGraph" Version="2.5.0" />
|
||||
<PackageReference Include="OneOf" Version="3.0.271" />
|
||||
<PackageReference Include="FluentResults" Version="3.16.0" />
|
||||
<PackageReference Include="Basic.Reference.Assemblies" Version="1.7.9" />
|
||||
<PackageReference Include="ImpromptuInterface " Version="8.0.4" />
|
||||
<ProjectReference Include="$(MSBuildThisFileDirectory)..\..\Libraries\moonsharp\MoonSharp.Interpreter\MoonSharp.Interpreter.csproj" />
|
||||
<ProjectReference Include="$(MSBuildThisFileDirectory)..\..\Libraries\moonsharp\MoonSharp.VsCodeDebugger\MoonSharp.VsCodeDebugger.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -1411,7 +1411,7 @@ namespace Barotrauma
|
||||
if (Components.Any(ic => ic is Wire) && Components.All(ic => ic is Wire || ic is Holdable)) { isWire = true; }
|
||||
if (HasTag(Barotrauma.Tags.LogicItem)) { isLogic = true; }
|
||||
|
||||
GameMain.LuaCs.Hook.Call("item.created", this);
|
||||
GameMain.LuaCs.Hook.Call<Item>("item.created", this);
|
||||
|
||||
ApplyStatusEffects(ActionType.OnSpawn, 1.0f);
|
||||
|
||||
@@ -4622,7 +4622,7 @@ namespace Barotrauma
|
||||
body = null;
|
||||
}
|
||||
|
||||
GameMain.LuaCs.Hook.Call("item.removed", this);
|
||||
GameMain.LuaCs.Hook.Call<Item>("item.removed", this);
|
||||
}
|
||||
|
||||
public override void Remove()
|
||||
@@ -4708,7 +4708,7 @@ namespace Barotrauma
|
||||
|
||||
RemoveProjSpecific();
|
||||
|
||||
GameMain.LuaCs.Hook.Call("item.removed", this);
|
||||
GameMain.LuaCs.Hook.Call<Item>("item.removed", this);
|
||||
}
|
||||
|
||||
private void RemoveFromLists()
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using Barotrauma.LuaCs.Data;
|
||||
using Barotrauma.Networking;
|
||||
|
||||
namespace Barotrauma.LuaCs.Configuration;
|
||||
@@ -13,9 +14,7 @@ public partial interface IConfigBase : IVarId
|
||||
void Initialize(IVarId id, string defaultValue);
|
||||
}
|
||||
|
||||
public interface IVarId
|
||||
public interface IVarId : IDataInfo
|
||||
{
|
||||
Guid InstanceId { get; }
|
||||
string InternalName { get; }
|
||||
ContentPackage OwnerPackage { get; }
|
||||
}
|
||||
|
||||
@@ -9,4 +9,6 @@ public interface IConfigEntry<T> : IConfigBase, INetVar where T : IConvertible,
|
||||
bool TrySetValue(T value);
|
||||
bool IsAssignable(T value);
|
||||
void Initialize(IVarId id, T defaultValue);
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace Barotrauma.LuaCs.Data;
|
||||
|
||||
@@ -29,6 +31,7 @@ public partial record ModConfigInfo : IModConfigInfo
|
||||
public record AssemblyResourceInfo : IAssemblyResourceInfo
|
||||
{
|
||||
public ContentPackage OwnerPackage { get; init; }
|
||||
public string FallbackPackageName { get; init; }
|
||||
public string FriendlyName { get; init; }
|
||||
public bool IsScript { get; init; }
|
||||
public string InternalName { get; init; }
|
||||
@@ -44,16 +47,109 @@ public record AssemblyResourceInfo : IAssemblyResourceInfo
|
||||
|
||||
public record DependencyInfo : IPackageDependencyInfo
|
||||
{
|
||||
public string InternalName { get; init; }
|
||||
public ContentPackage OwnerPackage { get; init; }
|
||||
public string FolderPath { get; init; }
|
||||
public string PackageName { get; init; }
|
||||
public string FallbackPackageName { get; init; }
|
||||
public ulong SteamWorkshopId { get; init; }
|
||||
public ContentPackage DependencyPackage { get; init; }
|
||||
public bool IsMissing { get; init; }
|
||||
public bool IsWorkshopInstallation { get; init; }
|
||||
|
||||
public virtual bool Equals(DependencyInfo other) => Equals(this, other);
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
if (DependencyPackage is not null)
|
||||
return DependencyPackage.GetHashCode();
|
||||
if (SteamWorkshopId != 0)
|
||||
return SteamWorkshopId.GetHashCode();
|
||||
if (!FallbackPackageName.IsNullOrWhiteSpace() && !FolderPath.IsNullOrWhiteSpace())
|
||||
return string.Concat(FallbackPackageName, FolderPath).GetHashCode();
|
||||
if (!InternalName.IsNullOrWhiteSpace() && !FolderPath.IsNullOrWhiteSpace())
|
||||
return string.Concat(InternalName, FolderPath).GetHashCode();
|
||||
|
||||
return base.GetHashCode();
|
||||
}
|
||||
|
||||
bool IEqualityComparer<IPackageDependencyInfo>.Equals(IPackageDependencyInfo x, IPackageDependencyInfo y) => DependencyInfo.Equals(x, y);
|
||||
|
||||
public static bool operator ==(IPackageDependencyInfo x, DependencyInfo y) => y?.Equals(x) ?? false;
|
||||
public static bool operator !=(IPackageDependencyInfo x, DependencyInfo y) => y?.Equals(x) ?? false;
|
||||
public static bool Equals(IPackageDependencyInfo x, IPackageDependencyInfo y)
|
||||
{
|
||||
if (x is null)
|
||||
return false;
|
||||
if (y is null)
|
||||
return false;
|
||||
if (x == y)
|
||||
return true;
|
||||
|
||||
if (x.DependencyPackage is not null && y.DependencyPackage is not null)
|
||||
return y.DependencyPackage == x.DependencyPackage;
|
||||
|
||||
if (!x.FolderPath.IsNullOrWhiteSpace()
|
||||
&& !y.FolderPath.IsNullOrWhiteSpace()
|
||||
&& y.FolderPath == x.FolderPath)
|
||||
return true;
|
||||
|
||||
if (!x.FolderPath.IsNullOrWhiteSpace() != !y.FolderPath.IsNullOrWhiteSpace())
|
||||
return false;
|
||||
|
||||
if (!x.FallbackPackageName.IsNullOrWhiteSpace()
|
||||
&& !y.FallbackPackageName.IsNullOrWhiteSpace()
|
||||
&& y.FallbackPackageName == x.FallbackPackageName)
|
||||
return true;
|
||||
|
||||
if (x.SteamWorkshopId != 0 && y.SteamWorkshopId == x.SteamWorkshopId)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the hash code unique for the package reference.
|
||||
/// </summary>
|
||||
/// <param name="obj"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>The hash should only be collision-free when referring to different packages.</remarks>
|
||||
public int GetHashCode(IPackageDependencyInfo obj)
|
||||
{
|
||||
int hashCode = Seed;
|
||||
hashCode = ApplyHashString(hashCode, obj.FallbackPackageName);
|
||||
hashCode = ApplyHashString(hashCode, obj.InternalName);
|
||||
if (obj.SteamWorkshopId > 0)
|
||||
hashCode ^= (int)obj.SteamWorkshopId;
|
||||
|
||||
|
||||
int ApplyHashString(int currentValue, string str)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (str is null || str.Length < 1)
|
||||
return currentValue;
|
||||
byte[] b = Encoding.UTF8.GetBytes(str);
|
||||
for (int i = 0; i < Math.Min(24, b.Length-1); i++)
|
||||
currentValue ^= b[i];
|
||||
return currentValue;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return currentValue;
|
||||
}
|
||||
}
|
||||
|
||||
return hashCode;
|
||||
}
|
||||
|
||||
private static readonly int Seed = new Random().Next(436457, int.MaxValue-900);
|
||||
}
|
||||
|
||||
public record LocalizationResourceInfo : ILocalizationResourceInfo
|
||||
{
|
||||
public string InternalName { get; init; }
|
||||
public ContentPackage OwnerPackage { get; init; }
|
||||
public string FallbackPackageName { get; init; }
|
||||
public CultureInfo TargetCulture { get; init; }
|
||||
public Platform SupportedPlatforms { get; init; }
|
||||
public Target SupportedTargets { get; init; }
|
||||
@@ -67,6 +163,7 @@ public record LocalizationResourceInfo : ILocalizationResourceInfo
|
||||
public readonly struct LuaScriptResourceInfo : ILuaResourceInfo
|
||||
{
|
||||
public ContentPackage OwnerPackage { get; init; }
|
||||
public string FallbackPackageName { get; init; }
|
||||
public Platform SupportedPlatforms { get; init; }
|
||||
public Target SupportedTargets { get; init; }
|
||||
public int LoadPriority { get; init; }
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
// ReSharper disable InconsistentNaming
|
||||
|
||||
namespace Barotrauma.LuaCs.Data;
|
||||
|
||||
@@ -6,7 +7,7 @@ namespace Barotrauma.LuaCs.Data;
|
||||
public enum Platform
|
||||
{
|
||||
Linux=0x1,
|
||||
OSX=0x2,
|
||||
MacOS=0x2,
|
||||
Windows=0x4
|
||||
}
|
||||
|
||||
|
||||
@@ -21,14 +21,10 @@ public interface IPlatformInfo
|
||||
Target SupportedTargets { get; }
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Which package does the following data belong to?
|
||||
/// All info we should have on a package for a given resource.
|
||||
/// </summary>
|
||||
public interface IPackageInfo
|
||||
{
|
||||
ContentPackage OwnerPackage { get; }
|
||||
}
|
||||
public interface IPackageInfo : IDataInfo { }
|
||||
|
||||
|
||||
/// <summary>
|
||||
@@ -65,13 +61,3 @@ public interface IResourceCultureInfo
|
||||
/// </summary>
|
||||
ImmutableArray<CultureInfo> SupportedCultures { get; }
|
||||
}
|
||||
|
||||
|
||||
public interface ILoadableResourceInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// [UNIQUE] The name that will be used when trying to reference this resource for execution or loading.
|
||||
/// </summary>
|
||||
[Required]
|
||||
public string InternalName { get; }
|
||||
}
|
||||
|
||||
@@ -1,26 +1,23 @@
|
||||
using System;
|
||||
using Barotrauma.LuaCs.Networking;
|
||||
using Barotrauma.Networking;
|
||||
|
||||
namespace Barotrauma.LuaCs.Data;
|
||||
|
||||
// TODO: Finish
|
||||
public partial interface IConfigInfo
|
||||
public partial interface IConfigInfo : IDataInfo
|
||||
{
|
||||
string Name { get; }
|
||||
string PackageName { get; }
|
||||
ConfigDataType Type { get; }
|
||||
/// <summary>
|
||||
/// Specifies the data type this should be initialized to (ie. string, int, vector, etc.)
|
||||
/// Custom types can be registered by mods.
|
||||
/// </summary>
|
||||
string DataType { get; }
|
||||
string DefaultValue { get; }
|
||||
string StoredValue { get; }
|
||||
ClientPermissions RequiredPermissions { get; }
|
||||
}
|
||||
|
||||
public enum ConfigDataType
|
||||
{
|
||||
Boolean, Int32, Int64, Single, Double, String,
|
||||
Color, Vector2, Vector3, List,
|
||||
RangeInt32, RangeSingle, ControlInput
|
||||
}
|
||||
|
||||
public enum NetSync
|
||||
{
|
||||
None, TwoWay, ServerAuthority, ClientOneWay
|
||||
/// <summary>
|
||||
/// Whether a value can be changed at runtime.
|
||||
/// </summary>
|
||||
bool IsReadOnly { get; }
|
||||
NetSync NetSync { get; }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
namespace Barotrauma.LuaCs.Data;
|
||||
|
||||
/// <summary>
|
||||
/// Serves as a compound-key to refer to all resources and information that comes from a specific source.
|
||||
/// </summary>
|
||||
public interface IDataInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Package-Unique name to be used internally for all representations of, and references to, this information.
|
||||
/// </summary>
|
||||
string InternalName { get; }
|
||||
/// <summary>
|
||||
/// The package this information belongs to.
|
||||
/// </summary>
|
||||
ContentPackage OwnerPackage { get; }
|
||||
/// <summary>
|
||||
/// Used in place of the package data when the OwnerPackage is missing.
|
||||
/// </summary>
|
||||
string FallbackPackageName { get; }
|
||||
}
|
||||
@@ -1,6 +1,12 @@
|
||||
namespace Barotrauma.LuaCs.Data;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
|
||||
public interface ILocalizationInfo
|
||||
namespace Barotrauma.LuaCs.Data;
|
||||
|
||||
public interface ILocalizationInfo : IDataInfo
|
||||
{
|
||||
|
||||
string Symbol { get; }
|
||||
IReadOnlyDictionary<CultureInfo, RawLString> LocalizedValues { get; }
|
||||
RawLString GetLocalizedString(CultureInfo locale);
|
||||
RawLString GetLocalizedString(string cultureCode);
|
||||
}
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
|
||||
namespace Barotrauma.LuaCs.Data;
|
||||
|
||||
public interface IPackageDependencyInfo : IPackageInfo
|
||||
public interface IPackageDependencyInfo : IPackageInfo,
|
||||
IEqualityComparer<IPackageDependencyInfo>
|
||||
{
|
||||
/// <summary>
|
||||
/// Root folder of the content package.
|
||||
/// </summary>
|
||||
public string FolderPath { get; }
|
||||
/// <summary>
|
||||
/// Name of the package.
|
||||
/// </summary>
|
||||
public string PackageName { get; }
|
||||
/// <summary>
|
||||
/// Steam ID of the package.
|
||||
/// </summary>
|
||||
public ulong SteamWorkshopId { get; }
|
||||
@@ -20,6 +19,16 @@ public interface IPackageDependencyInfo : IPackageInfo
|
||||
/// The dependency package, if found in the ALL Packages List.
|
||||
/// </summary>
|
||||
public ContentPackage DependencyPackage { get; }
|
||||
|
||||
/// <summary>
|
||||
/// This dependency was not found.
|
||||
/// </summary>
|
||||
public bool IsMissing { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether the package is installed from the workshop. False means installation is from local mods.
|
||||
/// </summary>
|
||||
public bool IsWorkshopInstallation { get; }
|
||||
}
|
||||
|
||||
public interface IPackageDependenciesInfo
|
||||
|
||||
@@ -10,8 +10,8 @@ public interface ILocalizationResourceInfo : IResourceInfo, IResourceCultureInfo
|
||||
/// <summary>
|
||||
/// Represents loadable Lua files.
|
||||
/// </summary>
|
||||
public interface ILuaResourceInfo : IResourceInfo, IResourceCultureInfo, IPackageDependenciesInfo, ILoadableResourceInfo, IPackageInfo { }
|
||||
public interface IAssemblyResourceInfo : IResourceInfo, IResourceCultureInfo, IPackageDependenciesInfo, ILoadableResourceInfo, IPackageInfo
|
||||
public interface ILuaResourceInfo : IResourceInfo, IResourceCultureInfo, IPackageDependenciesInfo, IPackageInfo { }
|
||||
public interface IAssemblyResourceInfo : IResourceInfo, IResourceCultureInfo, IPackageDependenciesInfo, IPackageInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// The friendly name of the assembly. Script files belonging to the same assembly should all have the same name.
|
||||
|
||||
212
Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs
Normal file
212
Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs
Normal file
@@ -0,0 +1,212 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using Barotrauma.LuaCs.Services;
|
||||
using Barotrauma.Networking;
|
||||
using Dynamitey;
|
||||
using ImpromptuInterface;
|
||||
|
||||
namespace Barotrauma.LuaCs.Events;
|
||||
|
||||
/*
|
||||
* The following is a collection of interfaces that types can implement to be registered events.
|
||||
* Note: Internally-marked interfaces should be consumed using a publicizer. This is due to the Barotrauma source
|
||||
* types being internal by default.
|
||||
*/
|
||||
|
||||
public interface IEvent
|
||||
{
|
||||
bool IsLuaRunner() => false;
|
||||
}
|
||||
|
||||
public interface IEvent<out T> : IEvent where T : IEvent<T>
|
||||
{
|
||||
static virtual T GetLuaRunner(IDictionary<string, LuaCsFunc> luaFunc)
|
||||
{
|
||||
// throw error if not overriden since we don't have 'static abstract'.
|
||||
// Implementers must provide the runner.
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
#region GameEvents
|
||||
|
||||
/// <summary>
|
||||
/// Called as soon as round begins to load before any loading takes place.
|
||||
/// </summary>
|
||||
public interface IEventRoundStarting : IEvent<IEventRoundStarting>
|
||||
{
|
||||
void OnRoundStarting();
|
||||
static IEventRoundStarting IEvent<IEventRoundStarting>.GetLuaRunner(IDictionary<string, LuaCsFunc> luaFunc) => new
|
||||
{
|
||||
IsLuaRunner = Return<bool>.Arguments(() => true),
|
||||
OnRoundStarting = ReturnVoid.Arguments(() => luaFunc[nameof(OnRoundStarting)]())
|
||||
}.ActLike<IEventRoundStarting>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when a round has started and fully loaded.
|
||||
/// </summary>
|
||||
public interface IEventRoundStarted : IEvent<IEventRoundStarted>
|
||||
{
|
||||
void OnRoundStart();
|
||||
static IEventRoundStarted IEvent<IEventRoundStarted>.GetLuaRunner(IDictionary<string, LuaCsFunc> luaFunc) => new
|
||||
{
|
||||
IsLuaRunner = Return<bool>.Arguments(() => true),
|
||||
OnRoundStart = ReturnVoid.Arguments(() => luaFunc[nameof(OnRoundStart)]())
|
||||
}.ActLike<IEventRoundStarted>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called on game loop normal update.
|
||||
/// </summary>
|
||||
public interface IEventUpdate : IEvent<IEventUpdate>
|
||||
{
|
||||
void OnUpdate(float fixedDeltaTime);
|
||||
static IEventUpdate IEvent<IEventUpdate>.GetLuaRunner(IDictionary<string, LuaCsFunc> luaFunc) => new
|
||||
{
|
||||
IsLuaRunner = Return<bool>.Arguments(() => true),
|
||||
OnUpdate = ReturnVoid.Arguments<float>((fixedDeltaTime) => luaFunc[nameof(OnUpdate)](fixedDeltaTime))
|
||||
}.ActLike<IEventUpdate>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called on game loop draw update.
|
||||
/// </summary>
|
||||
public interface IEventDrawUpdate : IEvent<IEventDrawUpdate>
|
||||
{
|
||||
void OnDrawUpdate(float deltaTime);
|
||||
static IEventDrawUpdate IEvent<IEventDrawUpdate>.GetLuaRunner(IDictionary<string, LuaCsFunc> luaFunc) => new
|
||||
{
|
||||
IsLuaRunner = Return<bool>.Arguments(() => true),
|
||||
OnDrawUpdate = ReturnVoid.Arguments<float>((deltaTime) => luaFunc[nameof(OnDrawUpdate)](deltaTime))
|
||||
}.ActLike<IEventDrawUpdate>();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Networking
|
||||
|
||||
|
||||
#region Networking-Server
|
||||
#if SERVER
|
||||
/// <summary>
|
||||
/// Called when a client connects to the server and has loaded into the lobby.
|
||||
/// </summary>
|
||||
interface IEventClientConnected : IEvent<IEventClientConnected>
|
||||
{
|
||||
/// <summary>
|
||||
/// Called when a client connects to the server.
|
||||
/// </summary>
|
||||
/// <param name="client">The connecting client.</param>
|
||||
void OnClientConnected(Client client);
|
||||
static IEventClientConnected IEvent<IEventClientConnected>.GetLuaRunner(IDictionary<string, LuaCsFunc> luaFunc) => new
|
||||
{
|
||||
IsLuaRunner = Return<bool>.Arguments(() => true),
|
||||
OnClientConnected = ReturnVoid.Arguments<Client>((client) => luaFunc[nameof(OnClientConnected)](client))
|
||||
}.ActLike<IEventClientConnected>();
|
||||
}
|
||||
#endif
|
||||
#endregion
|
||||
|
||||
#region Networking-Client
|
||||
#if CLIENT
|
||||
/// <summary>
|
||||
/// Called when the client has connected to the server and loaded to the lobby.
|
||||
/// </summary>
|
||||
public interface IEventServerConnected : IEvent<IEventServerConnected>
|
||||
{
|
||||
void OnServerConnected();
|
||||
static IEventServerConnected IEvent<IEventServerConnected>.GetLuaRunner(IDictionary<string, LuaCsFunc> luaFunc) => new
|
||||
{
|
||||
IsLuaRunner = Return<bool>.Arguments(() => true),
|
||||
OnServerConnected = ReturnVoid.Arguments(() => luaFunc[nameof(OnServerConnected)]())
|
||||
}.ActLike<IEventServerConnected>();
|
||||
}
|
||||
#endif
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
|
||||
#region Assembly_PluginEvents
|
||||
|
||||
/// <summary>
|
||||
/// Called on plugin normal, use this for basic/core loading that does not rely on any other modded content.
|
||||
/// </summary>
|
||||
public interface IEventPluginInitialize : IEvent<IEventPluginInitialize>
|
||||
{
|
||||
void Initialize();
|
||||
static IEventPluginInitialize IEvent<IEventPluginInitialize>.GetLuaRunner(IDictionary<string, LuaCsFunc> luaFunc) => new
|
||||
{
|
||||
IsLuaRunner = Return<bool>.Arguments(() => true),
|
||||
OnInitialize = ReturnVoid.Arguments(() => luaFunc[nameof(Initialize)]())
|
||||
}.ActLike<IEventPluginInitialize>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called once all plugins have been loaded. if you have integrations with any other mod, put that code here.
|
||||
/// </summary>
|
||||
public interface IEventPluginLoadCompleted : IEvent<IEventPluginLoadCompleted>
|
||||
{
|
||||
void OnLoadCompleted();
|
||||
static IEventPluginLoadCompleted IEvent<IEventPluginLoadCompleted>.GetLuaRunner(IDictionary<string, LuaCsFunc> luaFunc) => new
|
||||
{
|
||||
IsLuaRunner = Return<bool>.Arguments(() => true),
|
||||
OnLoadCompleted = ReturnVoid.Arguments(() => luaFunc[nameof(OnLoadCompleted)]())
|
||||
}.ActLike<IEventPluginLoadCompleted>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called before Barotrauma initializes plugins. Use if you want to patch another plugin's behaviour 'unofficially'.
|
||||
/// WARNING: This method is called before Initialize()!
|
||||
/// </summary>
|
||||
public interface IEventPluginPreInitialize : IEvent<IEventPluginPreInitialize>
|
||||
{
|
||||
void PreInitPatching();
|
||||
static IEventPluginPreInitialize IEvent<IEventPluginPreInitialize>.GetLuaRunner(IDictionary<string, LuaCsFunc> luaFunc) => new
|
||||
{
|
||||
IsLuaRunner = Return<bool>.Arguments(() => true),
|
||||
OnPreInitialize = ReturnVoid.Arguments(() => luaFunc[nameof(PreInitPatching)]())
|
||||
}.ActLike<IEventPluginPreInitialize>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called whenever a new assembly is loaded.
|
||||
/// </summary>
|
||||
public interface IEventAssemblyLoaded : IEvent<IEventAssemblyLoaded>
|
||||
{
|
||||
void OnAssemblyLoaded(Assembly assembly);
|
||||
static IEventAssemblyLoaded IEvent<IEventAssemblyLoaded>.GetLuaRunner(IDictionary<string, LuaCsFunc> luaFunc) => new
|
||||
{
|
||||
IsLuaRunner = Return<bool>.Arguments(() => true),
|
||||
OnAssemblyLoaded = ReturnVoid.Arguments<Assembly>((ass) => luaFunc[nameof(OnAssemblyLoaded)](ass))
|
||||
}.ActLike<IEventAssemblyLoaded>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called whenever an <see cref="IAssemblyLoaderService"/> is instanced.
|
||||
/// </summary>
|
||||
public interface IEventAssemblyContextCreated : IEvent<IEventAssemblyContextCreated>
|
||||
{
|
||||
void OnAssemblyCreated(IAssemblyLoaderService loaderService);
|
||||
static IEventAssemblyContextCreated IEvent<IEventAssemblyContextCreated>.GetLuaRunner(IDictionary<string, LuaCsFunc> luaFunc) => new
|
||||
{
|
||||
IsLuaRunner = Return<bool>.Arguments(() => true),
|
||||
OnAssemblyContextCreated = ReturnVoid.Arguments<IAssemblyLoaderService>((loader) => luaFunc[nameof(OnAssemblyCreated)](loader))
|
||||
}.ActLike<IEventAssemblyContextCreated>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called whenever an <see cref="IAssemblyLoaderService"/> begins unloading.
|
||||
/// </summary>
|
||||
public interface IEventAssemblyContextUnloading : IEvent<IEventAssemblyContextUnloading>
|
||||
{
|
||||
void OnAssemblyUnloading(WeakReference<IAssemblyLoaderService> loaderService);
|
||||
static IEventAssemblyContextUnloading IEvent<IEventAssemblyContextUnloading>.GetLuaRunner(IDictionary<string, LuaCsFunc> luaFunc) => new
|
||||
{
|
||||
IsLuaRunner = Return<bool>.Arguments(() => true),
|
||||
OnAssemblyUnloading = ReturnVoid.Arguments<WeakReference<IAssemblyLoaderService>>((loader) => luaFunc[nameof(OnAssemblyUnloading)](loader))
|
||||
}.ActLike<IEventAssemblyContextUnloading>();
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -463,7 +463,12 @@ namespace Barotrauma
|
||||
|
||||
public List<DebugConsole.Command> Commands => DebugConsole.Commands;
|
||||
|
||||
public void AssignOnExecute(string names, object onExecute) => DebugConsole.AssignOnExecute(names, (string[] a) => { GameMain.LuaCs.CallLuaFunction(onExecute, new object[] { a }); });
|
||||
public void AssignOnExecute(string names, object onExecute) => DebugConsole.AssignOnExecute(names,
|
||||
(string[] a) =>
|
||||
{
|
||||
//GameMain.LuaCs.CallLuaFunction(onExecute, new object[] { a });
|
||||
throw new NotImplementedException();
|
||||
});
|
||||
|
||||
public void SaveGame(string path)
|
||||
{
|
||||
|
||||
@@ -817,7 +817,7 @@ namespace Barotrauma
|
||||
if (luaCs.PerformanceCounter.EnablePerformanceCounter)
|
||||
{
|
||||
performanceMeasurement.Stop();
|
||||
luaCs.PerformanceCounter.SetHookElapsedTicks(name, key, performanceMeasurement.ElapsedTicks);
|
||||
//luaCs.PerformanceCounter.SetHookElapsedTicks(name, key, performanceMeasurement.ElapsedTicks); TODO
|
||||
performanceMeasurement.Reset();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,114 +0,0 @@
|
||||
using MoonSharp.Interpreter;
|
||||
using MoonSharp.Interpreter.Interop;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class LuaCsSetup
|
||||
{
|
||||
public class LuaCsModStore
|
||||
{
|
||||
public abstract class ModStore<T, TStore>
|
||||
{
|
||||
protected Dictionary<string, TStore> store;
|
||||
|
||||
public TStore Set(string name, TStore value) => store[name] = value;
|
||||
public TStore Get(string name) => store[name];
|
||||
|
||||
public ModStore(Dictionary<string, TStore> store) => this.store = store;
|
||||
|
||||
public abstract bool Equals(T value);
|
||||
}
|
||||
public class LuaModStore : ModStore<string, DynValue>
|
||||
{
|
||||
public string Name;
|
||||
|
||||
public LuaModStore(Dictionary<string, DynValue> store) : base(store) { }
|
||||
public override bool Equals(string value) => Name == value;
|
||||
}
|
||||
public class CsModStore : ModStore<ACsMod, object>
|
||||
{
|
||||
public ACsMod Mod;
|
||||
|
||||
public CsModStore(Dictionary<string, object> store) : base(store) { }
|
||||
public override bool Equals(ACsMod value) => Mod == value;
|
||||
}
|
||||
|
||||
private HashSet<LuaModStore> luaModInterface;
|
||||
private HashSet<CsModStore> csModInterface;
|
||||
|
||||
public LuaCsModStore()
|
||||
{
|
||||
luaModInterface = new HashSet<LuaModStore>();
|
||||
csModInterface = new HashSet<CsModStore>();
|
||||
}
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
UserData.RegisterType<LuaModStore>();
|
||||
UserData.RegisterType<CsModStore>();
|
||||
var msType = UserData.RegisterType<LuaCsModStore>();
|
||||
var msDesc = (StandardUserDataDescriptor)msType;
|
||||
|
||||
typeof(StandardUserDataDescriptor).GetMethods(BindingFlags.NonPublic | BindingFlags.Instance).ToList().ForEach(m =>
|
||||
{
|
||||
if (
|
||||
m.Name.Contains("Register")
|
||||
)
|
||||
{
|
||||
msDesc.AddMember(m.Name, new MethodMemberDescriptor(m, InteropAccessMode.Default));
|
||||
}
|
||||
});
|
||||
}
|
||||
public void Clear()
|
||||
{
|
||||
luaModInterface.Clear();
|
||||
csModInterface.Clear();
|
||||
}
|
||||
|
||||
protected LuaModStore Register(string modName)
|
||||
{
|
||||
if (luaModInterface.Any(i => i.Equals(modName)))
|
||||
{
|
||||
LuaCsLogger.HandleException(new ArgumentException($"'{modName}' entry already registered"), LuaCsMessageOrigin.LuaMod);
|
||||
return null;
|
||||
}
|
||||
|
||||
var newHandle = new LuaModStore(new Dictionary<string, DynValue>());
|
||||
if (luaModInterface.Add(newHandle)) return newHandle;
|
||||
else return null;
|
||||
}
|
||||
[MoonSharpHidden]
|
||||
public CsModStore Register(ACsMod mod)
|
||||
{
|
||||
if (csModInterface.Any(i => i.Equals(mod)))
|
||||
{
|
||||
LuaCsLogger.HandleException(new ArgumentException($"'{mod.GetType().FullName}' entry already registered"), LuaCsMessageOrigin.CSharpMod);
|
||||
return null;
|
||||
}
|
||||
|
||||
var newHandle = new CsModStore(new Dictionary<string, object>());
|
||||
if (csModInterface.Add(newHandle)) return newHandle;
|
||||
else return null;
|
||||
}
|
||||
|
||||
public CsModStore GetCsStore(string modName) {
|
||||
var result = csModInterface.Where(i => i.Mod.GetType().FullName == modName).FirstOrDefault();
|
||||
if (result != null)
|
||||
{
|
||||
if (!result.Mod.IsDisposed) return result;
|
||||
else
|
||||
{
|
||||
csModInterface.Remove(result);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else return null;
|
||||
}
|
||||
protected LuaModStore GetLuaStore(string modName) => luaModInterface.Where(i => i.Name == modName).FirstOrDefault();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,36 +1,81 @@
|
||||
using Barotrauma.LuaCs.Services;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public class LuaCsPerformanceCounter
|
||||
public interface IPerformanceData
|
||||
{
|
||||
public bool EnablePerformanceCounter = false;
|
||||
public string Identifier { get; }
|
||||
public long ElapsedTicks { get; }
|
||||
}
|
||||
|
||||
public double UpdateElapsedTime;
|
||||
public Dictionary<string, Dictionary<string, double>> HookElapsedTime = new Dictionary<string, Dictionary<string, double>>();
|
||||
public class SimplePerformanceData : IPerformanceData
|
||||
{
|
||||
public string Identifier { get; }
|
||||
public long ElapsedTicks { get; }
|
||||
|
||||
public static float MemoryUsage
|
||||
public SimplePerformanceData(string identifier, long elapsedTicks)
|
||||
{
|
||||
get
|
||||
{
|
||||
Process proc = Process.GetCurrentProcess();
|
||||
float memory = MathF.Round(proc.PrivateMemorySize64 / (1024 * 1024), 2);
|
||||
proc.Dispose();
|
||||
|
||||
return memory;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetHookElapsedTicks(string eventName, string hookName, long ticks)
|
||||
{
|
||||
if (!HookElapsedTime.ContainsKey(eventName))
|
||||
{
|
||||
HookElapsedTime[eventName] = new Dictionary<string, double>();
|
||||
}
|
||||
|
||||
HookElapsedTime[eventName][hookName] = (double)ticks / Stopwatch.Frequency;
|
||||
Identifier = identifier;
|
||||
ElapsedTicks = elapsedTicks;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class PerformanceCounterService : IReusableService
|
||||
{
|
||||
public bool EnablePerformanceCounter { get; set; } = false;
|
||||
|
||||
private Dictionary<string, List<IPerformanceData>> _data = new Dictionary<string, List<IPerformanceData>>();
|
||||
|
||||
public void AddElapsedTicks(IPerformanceData data)
|
||||
{
|
||||
if (!EnablePerformanceCounter) { return; }
|
||||
|
||||
if (!_data.ContainsKey(data.Identifier))
|
||||
{
|
||||
_data.Add(data.Identifier, new List<IPerformanceData>());
|
||||
}
|
||||
|
||||
_data[data.Identifier].Add(data);
|
||||
|
||||
Trim(data.Identifier, 100);
|
||||
}
|
||||
|
||||
public T GetLatestSnapshot<T>(string identifier) where T : class, IPerformanceData
|
||||
{
|
||||
if (!_data.ContainsKey(identifier)) { return default; }
|
||||
|
||||
return (T)_data[identifier].Last();
|
||||
}
|
||||
|
||||
public T[] GetSnapshot<T>(string identifier, int length) where T : class, IPerformanceData, new()
|
||||
{
|
||||
if (!_data.ContainsKey(identifier)) { return new T[] { }; }
|
||||
|
||||
length = Math.Min(length, _data[identifier].Count);
|
||||
|
||||
return _data[identifier].GetRange(_data[identifier].Count - length, length).Cast<T>().ToArray();
|
||||
}
|
||||
|
||||
public void Trim(string identifier, int maxSize)
|
||||
{
|
||||
if (!_data.ContainsKey(identifier)) { return; }
|
||||
|
||||
if (_data[identifier].Count > maxSize)
|
||||
{
|
||||
_data[identifier].RemoveRange(0, _data[identifier].Count - maxSize);
|
||||
}
|
||||
}
|
||||
|
||||
public FluentResults.Result Reset()
|
||||
{
|
||||
_data = new Dictionary<string, List<IPerformanceData>>();
|
||||
return FluentResults.Result.Ok();
|
||||
}
|
||||
|
||||
public void Dispose() { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,9 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using MoonSharp.Interpreter;
|
||||
using MoonSharp.Interpreter.Interop;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using LuaCsCompatPatchFunc = Barotrauma.LuaCsPatch;
|
||||
using System.Diagnostics;
|
||||
using MoonSharp.VsCodeDebugger;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Loader;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.LuaCs.Configuration;
|
||||
using Barotrauma.LuaCs.Services;
|
||||
using Barotrauma.LuaCs.Services.Compatibility;
|
||||
using Barotrauma.LuaCs.Services.Processing;
|
||||
using Barotrauma.Networking;
|
||||
|
||||
namespace Barotrauma
|
||||
@@ -42,23 +34,98 @@ namespace Barotrauma
|
||||
internal delegate void LuaCsErrorHandler(Exception ex, LuaCsMessageOrigin origin);
|
||||
internal delegate void LuaCsExceptionHandler(Exception ex, LuaCsMessageOrigin origin);
|
||||
|
||||
partial class LuaCsSetup
|
||||
partial class LuaCsSetup : IDisposable
|
||||
{
|
||||
public const string LuaSetupFile = "Lua/LuaSetup.lua";
|
||||
public const string VersionFile = "luacsversion.txt";
|
||||
#if WINDOWS
|
||||
public static ContentPackageId LuaForBarotraumaId = new SteamWorkshopId(2559634234);
|
||||
#elif LINUX
|
||||
public static ContentPackageId LuaForBarotraumaId = new SteamWorkshopId(2970628943);
|
||||
#elif OSX
|
||||
public static ContentPackageId LuaForBarotraumaId = new SteamWorkshopId(2970890020);
|
||||
#endif
|
||||
public LuaCsSetup()
|
||||
{
|
||||
// load services
|
||||
_servicesProvider = new ServicesProvider();
|
||||
RegisterServices();
|
||||
|
||||
// load manifest
|
||||
if (!_servicesProvider.TryGetService(out IModConfigParserService modConfigSvc))
|
||||
throw new NullReferenceException("LuaCsSetup: Failed to get mod config parser service!"); // we should crash here
|
||||
var luaConfig = modConfigSvc.BuildConfigFromManifest(Directory.GetCurrentDirectory() + LuaCsConfigFile);
|
||||
if (!luaConfig.IsSuccess)
|
||||
{
|
||||
Logger.LogResults(luaConfig.ToResult());
|
||||
throw new FileLoadException("LuaCsSetup: Failed to load config file!");
|
||||
}
|
||||
|
||||
// load resources
|
||||
RegisterLocalizations();
|
||||
RegisterConfigs();
|
||||
|
||||
public static ContentPackageId CsForBarotraumaId = new SteamWorkshopId(2795927223);
|
||||
LuaForBarotraumaId = new SteamWorkshopId(LuaForBarotraumaSteamId.Value);
|
||||
|
||||
return;
|
||||
//---
|
||||
|
||||
void RegisterServices()
|
||||
{
|
||||
_servicesProvider.RegisterServiceType<ILoggerService, LoggerService>(ServiceLifetime.Singleton);
|
||||
_servicesProvider.RegisterServiceType<PerformanceCounterService, PerformanceCounterService>(ServiceLifetime.Singleton);
|
||||
_servicesProvider.RegisterServiceType<IPackageService, PackageService>(ServiceLifetime.Singleton);
|
||||
_servicesProvider.RegisterServiceType<IPackageManagementService, PackageManagementService>(ServiceLifetime.Singleton);
|
||||
_servicesProvider.RegisterServiceType<ILuaScriptService, LuaScriptService>(ServiceLifetime.Singleton);
|
||||
_servicesProvider.RegisterServiceType<ILuaScriptManagementService, LuaScriptService>(ServiceLifetime.Singleton);
|
||||
// TODO: IConfigService
|
||||
// TODO: INetworkingService
|
||||
// TODO: [Resource Converter/Parser Services]
|
||||
// TODO: ILocalizationService
|
||||
// TODO: IEventService
|
||||
_servicesProvider.Compile();
|
||||
}
|
||||
|
||||
void RegisterLocalizations()
|
||||
{
|
||||
LocalizationService.LoadLocalizations(luaConfig.Value.Localizations);
|
||||
}
|
||||
|
||||
private const string configFileName = "LuaCsSetupConfig.xml";
|
||||
void RegisterConfigs()
|
||||
{
|
||||
if (ConfigService.AddConfigs(luaConfig.Value.Configs) is { IsSuccess: false } res1)
|
||||
{
|
||||
Logger.LogResults(res1);
|
||||
throw new Exception("LuaCsSetup: Failed to load config!");
|
||||
}
|
||||
|
||||
if (ConfigService.AddConfigsProfiles(luaConfig.Value.ConfigProfiles) is { IsSuccess: false } res2)
|
||||
{
|
||||
Logger.LogResults(res2);
|
||||
throw new Exception("LuaCsSetup: Failed to load config profiles!");
|
||||
}
|
||||
|
||||
IsCsEnabled = GetOrThrowForConfig<bool>(luaConfig.Value.PackageName, "IsCsEnabled");
|
||||
TreatForcedModsAsNormal = GetOrThrowForConfig<bool>(luaConfig.Value.PackageName, "TreatForcedModsAsNormal");
|
||||
PreferToUseWorkshopLuaSetup = GetOrThrowForConfig<bool>(luaConfig.Value.PackageName, "PreferToUseWorkshopLuaSetup");
|
||||
DisableErrorGUIOverlay = GetOrThrowForConfig<bool>(luaConfig.Value.PackageName, "DisableErrorGUIOverlay");
|
||||
EnableThreadedLoading = GetOrThrowForConfig<bool>(luaConfig.Value.PackageName, "EnableThreadedLoading");
|
||||
HideUserNamesInLogs = GetOrThrowForConfig<bool>(luaConfig.Value.PackageName, "HideUserNamesInLogs");
|
||||
LuaForBarotraumaSteamId = GetOrThrowForConfig<ulong>(luaConfig.Value.PackageName, "LuaForBarotraumaSteamId");
|
||||
|
||||
return;
|
||||
//---
|
||||
|
||||
IConfigEntry<T> GetOrThrowForConfig<T>(string packName, string internalName) where T : IConvertible, IEquatable<T>
|
||||
{
|
||||
var cfgRes = ConfigService.GetConfig<IConfigEntry<T>>(packName, internalName);
|
||||
if (cfgRes.IsSuccess)
|
||||
{
|
||||
return cfgRes.Value;
|
||||
}
|
||||
Logger.LogResults(cfgRes.ToResult());
|
||||
throw new Exception($"LuaCsSetup: Failed to load config for {internalName}!");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
#region CONST_DEF
|
||||
|
||||
public const string LuaCsConfigFile = "LuaCsConfig.xml";
|
||||
|
||||
#if SERVER
|
||||
public const bool IsServer = true;
|
||||
public const bool IsClient = false;
|
||||
@@ -67,6 +134,87 @@ namespace Barotrauma
|
||||
public const bool IsClient = true;
|
||||
#endif
|
||||
|
||||
#endregion
|
||||
|
||||
#region Services_ConfigVars
|
||||
|
||||
/*
|
||||
* === Singleton Services
|
||||
*/
|
||||
|
||||
private readonly IServicesProvider _servicesProvider;
|
||||
|
||||
public PerformanceCounterService PerformanceCounter => _servicesProvider.TryGetService<PerformanceCounterService>(out var svc)
|
||||
? svc : throw new NullReferenceException("Performance counter service not found!");
|
||||
public ILoggerService Logger => _servicesProvider.TryGetService<ILoggerService>(out var svc)
|
||||
? svc : throw new NullReferenceException("Logger service not found!");
|
||||
public IConfigService ConfigService => _servicesProvider.TryGetService<IConfigService>(out var svc)
|
||||
? svc : throw new NullReferenceException("Config Manager service not found!");
|
||||
public IPackageManagementService PackageManagementService => _servicesProvider.TryGetService<IPackageManagementService>(out var svc)
|
||||
? svc : throw new NullReferenceException("Package Manager service not found!");
|
||||
public IPluginManagementService PluginManagementService => _servicesProvider.TryGetService<IPluginManagementService>(out var svc)
|
||||
? svc : throw new NullReferenceException("Plugin Manager service not found!");
|
||||
public ILuaScriptManagementService LuaScriptService => _servicesProvider.TryGetService<ILuaScriptManagementService>(out var svc)
|
||||
? svc : throw new NullReferenceException("Lua Script Manager service not found!");
|
||||
public ILocalizationService LocalizationService => _servicesProvider.TryGetService<ILocalizationService>(out var svc)
|
||||
? svc : throw new NullReferenceException("Localization Manager service not found!");
|
||||
public INetworkingService NetworkingService => _servicesProvider.TryGetService<INetworkingService>(out var svc)
|
||||
? svc : throw new NullReferenceException("Networking Manager service not found!");
|
||||
public IEventService EventService => _servicesProvider.TryGetService<IEventService>(out var svc)
|
||||
? svc : throw new NullReferenceException("Networking Manager service not found!");
|
||||
|
||||
/*
|
||||
* === Config Vars
|
||||
*/
|
||||
|
||||
/// <summary>
|
||||
/// Whether C# plugin code is enabled.
|
||||
/// </summary>
|
||||
public IConfigEntry<bool> IsCsEnabled { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether mods marked as 'forced' or 'always load' should only be loaded if they're in the enabled mods list.
|
||||
/// </summary>
|
||||
public IConfigEntry<bool> TreatForcedModsAsNormal { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether the lua script runner from Workshop package should be used over the in-built version.
|
||||
/// </summary>
|
||||
public IConfigEntry<bool> PreferToUseWorkshopLuaSetup { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether the popup error GUI should be hidden/suppressed.
|
||||
/// </summary>
|
||||
public IConfigEntry<bool> DisableErrorGUIOverlay { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// [Experimental] Whether multithreading should be used for loading.
|
||||
/// </summary>
|
||||
public IConfigEntry<bool> EnableThreadedLoading { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether usernames are anonymized or show in logs.
|
||||
/// </summary>
|
||||
public IConfigEntry<bool> HideUserNamesInLogs { get; private set; }
|
||||
|
||||
private IConfigEntry<ulong> LuaForBarotraumaSteamId { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region LegacyRedirects
|
||||
|
||||
public ILuaCsHook Hook => this.EventService;
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Whether mod content is loaded and being executed.
|
||||
/// </summary>
|
||||
public bool IsModContentRunning { get; private set; }
|
||||
|
||||
public readonly ContentPackageId LuaForBarotraumaId;
|
||||
|
||||
public static bool IsRunningInsideWorkshop
|
||||
{
|
||||
get
|
||||
@@ -79,10 +227,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private static int executionNumber = 0;
|
||||
|
||||
|
||||
public Script Lua { get; private set; }
|
||||
/*public Script Lua { get; private set; }
|
||||
public LuaScriptLoader LuaScriptLoader { get; private set; }
|
||||
|
||||
public LuaGame Game { get; private set; }
|
||||
@@ -90,7 +235,6 @@ namespace Barotrauma
|
||||
public LuaCsTimer Timer { get; private set; }
|
||||
public LuaCsNetworking Networking { get; private set; }
|
||||
public LuaCsSteam Steam { get; private set; }
|
||||
public LuaCsPerformanceCounter PerformanceCounter { get; private set; }
|
||||
|
||||
// must be available at anytime
|
||||
private static AssemblyManager _assemblyManager;
|
||||
@@ -98,12 +242,10 @@ namespace Barotrauma
|
||||
|
||||
private CsPackageManager _pluginPackageManager;
|
||||
public CsPackageManager PluginPackageManager => _pluginPackageManager ??= new CsPackageManager(AssemblyManager, this);
|
||||
|
||||
public LuaCsModStore ModStore { get; private set; }
|
||||
private LuaRequire Require { get; set; }
|
||||
public LuaCsSetupConfig Config { get; private set; }
|
||||
public MoonSharpVsCodeDebugServer DebugServer { get; private set; }
|
||||
public bool IsInitialized { get; private set; }
|
||||
public bool IsInitialized { get; private set; }*/
|
||||
|
||||
private bool ShouldRunCs
|
||||
{
|
||||
@@ -112,64 +254,21 @@ namespace Barotrauma
|
||||
#if SERVER
|
||||
if (GetPackage(CsForBarotraumaId, false, false) != null && GameMain.Server.ServerPeer is LidgrenServerPeer) { return true; }
|
||||
#endif
|
||||
|
||||
return Config.EnableCsScripting;
|
||||
return IsCsEnabled.Value;
|
||||
}
|
||||
}
|
||||
|
||||
public LuaCsSetup()
|
||||
{
|
||||
Script.GlobalOptions.Platform = new LuaPlatformAccessor();
|
||||
|
||||
Hook = new LuaCsHook(this);
|
||||
ModStore = new LuaCsModStore();
|
||||
|
||||
Game = new LuaGame();
|
||||
Networking = new LuaCsNetworking();
|
||||
DebugServer = new MoonSharpVsCodeDebugServer();
|
||||
|
||||
ReadSettings();
|
||||
}
|
||||
|
||||
|
||||
[Obsolete("Use AssemblyManager::GetTypesByName()")]
|
||||
public static Type GetType(string typeName, bool throwOnError = false, bool ignoreCase = false)
|
||||
{
|
||||
return AssemblyManager.GetTypesByName(typeName).FirstOrDefault((Type)null);
|
||||
throw new NotImplementedException();
|
||||
//return AssemblyManager.GetTypesByName(typeName).FirstOrDefault((Type)null);
|
||||
}
|
||||
|
||||
public void ToggleDebugger(int port = 41912)
|
||||
{
|
||||
if (!GameMain.LuaCs.DebugServer.IsStarted)
|
||||
{
|
||||
DebugServer.Start();
|
||||
AttachDebugger();
|
||||
|
||||
LuaCsLogger.Log($"Lua Debug Server started on port {port}.");
|
||||
}
|
||||
else
|
||||
{
|
||||
DetachDebugger();
|
||||
DebugServer.Stop();
|
||||
|
||||
LuaCsLogger.Log($"Lua Debug Server stopped.");
|
||||
}
|
||||
}
|
||||
|
||||
public void AttachDebugger()
|
||||
{
|
||||
DebugServer.AttachToScript(Lua, "Script", s =>
|
||||
{
|
||||
if (s.Name.StartsWith("LocalMods") || s.Name.StartsWith("Lua"))
|
||||
{
|
||||
return Environment.CurrentDirectory + "/" + s.Name;
|
||||
}
|
||||
return s.Name;
|
||||
});
|
||||
}
|
||||
|
||||
public void DetachDebugger() => DebugServer.Detach(Lua);
|
||||
|
||||
public void ReadSettings()
|
||||
|
||||
// Old config ref
|
||||
/*public void ReadSettings()
|
||||
{
|
||||
Config = new LuaCsSetupConfig();
|
||||
|
||||
@@ -205,7 +304,7 @@ namespace Barotrauma
|
||||
document.Root.SetAttributeValue("DisableErrorGUIOverlay", Config.DisableErrorGUIOverlay);
|
||||
document.Root.SetAttributeValue("HideUserNames", Config.HideUserNames);
|
||||
document.Save(configFileName);
|
||||
}
|
||||
}*/
|
||||
|
||||
public static ContentPackage GetPackage(ContentPackageId id, bool fallbackToAll = true, bool useBackup = false)
|
||||
{
|
||||
@@ -250,7 +349,8 @@ namespace Barotrauma
|
||||
return null;
|
||||
}
|
||||
|
||||
private DynValue DoFile(string file, Table globalContext = null, string codeStringFriendly = null)
|
||||
// Old code ref
|
||||
/*private DynValue DoFile(string file, Table globalContext = null, string codeStringFriendly = null)
|
||||
{
|
||||
if (!LuaCsFile.CanReadFromPath(file))
|
||||
{
|
||||
@@ -324,51 +424,7 @@ namespace Barotrauma
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
PluginPackageManager.UnloadPlugins();
|
||||
|
||||
// unregister types
|
||||
foreach (Type type in AssemblyManager.GetAllLoadedACLs().SelectMany(
|
||||
acl => acl.AssembliesTypes.Select(kvp => kvp.Value)))
|
||||
{
|
||||
UserData.UnregisterType(type, true);
|
||||
}
|
||||
|
||||
if (Lua?.Globals is not null)
|
||||
{
|
||||
Lua.Globals.Remove("CsPackageManager");
|
||||
Lua.Globals.Remove("AssemblyManager");
|
||||
}
|
||||
|
||||
if (Thread.CurrentThread == GameMain.MainThread)
|
||||
{
|
||||
Hook?.Call("stop");
|
||||
}
|
||||
|
||||
if (Lua != null && DebugServer.IsStarted)
|
||||
{
|
||||
DebugServer.Detach(Lua);
|
||||
}
|
||||
|
||||
LuaUserData.Clear();
|
||||
|
||||
Game?.Stop();
|
||||
|
||||
Hook?.Clear();
|
||||
ModStore.Clear();
|
||||
LuaScriptLoader = null;
|
||||
Lua = null;
|
||||
|
||||
// we can only unload assemblies after clearing ModStore/references.
|
||||
PluginPackageManager.Dispose();
|
||||
#pragma warning disable CS0618
|
||||
ACsMod.LoadedMods.Clear();
|
||||
#pragma warning restore CS0618
|
||||
|
||||
Game = new LuaGame();
|
||||
Networking = new LuaCsNetworking();
|
||||
Timer = new LuaCsTimer();
|
||||
Steam = new LuaCsSteam();
|
||||
PerformanceCounter = new LuaCsPerformanceCounter();
|
||||
|
||||
IsInitialized = false;
|
||||
}
|
||||
@@ -382,170 +438,22 @@ namespace Barotrauma
|
||||
|
||||
IsInitialized = true;
|
||||
|
||||
LuaCsLogger.LogMessage("Lua! Version " + AssemblyInfo.GitRevision);
|
||||
Logger.Log($"Initializing LuaCs, git revision = {AssemblyInfo.GitRevision}");
|
||||
}*/
|
||||
|
||||
public void Update()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
bool csActive = ShouldRunCs || forceEnableCs;
|
||||
public void Reset()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
LuaScriptLoader = new LuaScriptLoader();
|
||||
LuaScriptLoader.ModulePaths = new string[] { };
|
||||
|
||||
RegisterLuaConverters();
|
||||
|
||||
Lua = new Script(CoreModules.Preset_SoftSandbox | CoreModules.Debug | CoreModules.IO | CoreModules.OS_System);
|
||||
Lua.Options.DebugPrint = (o) => { LuaCsLogger.LogMessage(o); };
|
||||
Lua.Options.ScriptLoader = LuaScriptLoader;
|
||||
Lua.Options.CheckThreadAccess = false;
|
||||
Script.GlobalOptions.ShouldPCallCatchException = (Exception ex) => { return true; };
|
||||
|
||||
Require = new LuaRequire(Lua);
|
||||
|
||||
Game = new LuaGame();
|
||||
Networking = new LuaCsNetworking();
|
||||
Timer = new LuaCsTimer();
|
||||
Steam = new LuaCsSteam();
|
||||
PerformanceCounter = new LuaCsPerformanceCounter();
|
||||
Hook.Initialize();
|
||||
ModStore.Initialize();
|
||||
Networking.Initialize();
|
||||
|
||||
UserData.RegisterType<LuaCsLogger>();
|
||||
UserData.RegisterType<LuaCsConfig>();
|
||||
UserData.RegisterType<LuaCsSetupConfig>();
|
||||
UserData.RegisterType<LuaCsAction>();
|
||||
UserData.RegisterType<LuaCsFile>();
|
||||
UserData.RegisterType<LuaCsCompatPatchFunc>();
|
||||
UserData.RegisterType<LuaCsPatchFunc>();
|
||||
UserData.RegisterType<LuaGame>();
|
||||
UserData.RegisterType<LuaCsTimer>();
|
||||
UserData.RegisterType<LuaCsFile>();
|
||||
UserData.RegisterType<LuaCsNetworking>();
|
||||
UserData.RegisterType<LuaCsSteam>();
|
||||
var uuid = UserData.RegisterType<LuaUserData>();
|
||||
UserData.RegisterType<LuaCsPerformanceCounter>();
|
||||
UserData.RegisterType<IUserDataDescriptor>();
|
||||
|
||||
Lua.Globals["printerror"] = (DynValue o) => { LuaCsLogger.LogError(o.ToString(), LuaCsMessageOrigin.LuaMod); };
|
||||
|
||||
Lua.Globals["setmodulepaths"] = (Action<string[]>)SetModulePaths;
|
||||
|
||||
Lua.Globals["dofile"] = (Func<string, Table, string, DynValue>)DoFile;
|
||||
Lua.Globals["loadfile"] = (Func<string, Table, string, DynValue>)LoadFile;
|
||||
Lua.Globals["require"] = (Func<string, Table, DynValue>)Require.Require;
|
||||
|
||||
Lua.Globals["dostring"] = (Func<string, Table, string, DynValue>)Lua.DoString;
|
||||
Lua.Globals["load"] = (Func<string, Table, string, DynValue>)Lua.LoadString;
|
||||
|
||||
Lua.Globals["Logger"] = UserData.CreateStatic<LuaCsLogger>();
|
||||
Lua.Globals["LuaUserData"] = UserData.CreateStatic<LuaUserData>();
|
||||
Lua.Globals["LuaUserDataIUUD"] = uuid;
|
||||
Lua.Globals["Game"] = Game;
|
||||
Lua.Globals["Hook"] = Hook;
|
||||
Lua.Globals["ModStore"] = ModStore;
|
||||
Lua.Globals["Timer"] = Timer;
|
||||
Lua.Globals["File"] = UserData.CreateStatic<LuaCsFile>();
|
||||
Lua.Globals["Networking"] = Networking;
|
||||
Lua.Globals["Steam"] = Steam;
|
||||
Lua.Globals["PerformanceCounter"] = PerformanceCounter;
|
||||
Lua.Globals["LuaCsConfig"] = new LuaCsSetupConfig(Config);
|
||||
|
||||
Lua.Globals["ExecutionNumber"] = executionNumber;
|
||||
Lua.Globals["CSActive"] = csActive;
|
||||
|
||||
Lua.Globals["SERVER"] = IsServer;
|
||||
Lua.Globals["CLIENT"] = IsClient;
|
||||
|
||||
if (DebugServer.IsStarted)
|
||||
{
|
||||
AttachDebugger();
|
||||
}
|
||||
|
||||
if (csActive)
|
||||
{
|
||||
LuaCsLogger.LogMessage("Cs! Version " + AssemblyInfo.GitRevision);
|
||||
|
||||
UserData.RegisterType<CsPackageManager>();
|
||||
UserData.RegisterType<AssemblyManager>();
|
||||
UserData.RegisterType<IAssemblyPlugin>();
|
||||
|
||||
Lua.Globals["PluginPackageManager"] = PluginPackageManager;
|
||||
Lua.Globals["AssemblyManager"] = AssemblyManager;
|
||||
|
||||
try
|
||||
{
|
||||
Stopwatch taskTimer = new();
|
||||
taskTimer.Start();
|
||||
ModStore.Clear();
|
||||
|
||||
var state = PluginPackageManager.LoadAssemblyPackages();
|
||||
if (state is AssemblyLoadingSuccessState.Success or AssemblyLoadingSuccessState.AlreadyLoaded)
|
||||
{
|
||||
if(!PluginPackageManager.PluginsInitialized)
|
||||
PluginPackageManager.InstantiatePlugins(true);
|
||||
if(!PluginPackageManager.PluginsPreInit)
|
||||
PluginPackageManager.RunPluginsPreInit(); // this is intended to be called at startup in the future
|
||||
if(!PluginPackageManager.PluginsLoaded)
|
||||
PluginPackageManager.RunPluginsInit();
|
||||
state = AssemblyLoadingSuccessState.Success;
|
||||
taskTimer.Stop();
|
||||
ModUtils.Logging.PrintMessage($"{nameof(LuaCsSetup)}: Completed assembly loading. Total time {taskTimer.ElapsedMilliseconds}ms.");
|
||||
}
|
||||
else
|
||||
{
|
||||
PluginPackageManager.Dispose(); // cleanup if there's an error
|
||||
}
|
||||
|
||||
if(state is not AssemblyLoadingSuccessState.Success)
|
||||
{
|
||||
ModUtils.Logging.PrintError($"{nameof(LuaCsSetup)}: Error while loading Cs-Assembly Mods | Err: {state}");
|
||||
taskTimer.Stop();
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ModUtils.Logging.PrintError($"{nameof(LuaCsSetup)}::{nameof(Initialize)}() | Error while loading assemblies! Details: {e.Message} | {e.StackTrace}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ContentPackage luaPackage = GetPackage(LuaForBarotraumaId);
|
||||
|
||||
void RunLocal()
|
||||
{
|
||||
LuaCsLogger.LogMessage("Using LuaSetup.lua from the Barotrauma Lua/ folder.");
|
||||
string luaPath = LuaSetupFile;
|
||||
CallLuaFunction(Lua.LoadFile(luaPath), Path.GetDirectoryName(Path.GetFullPath(luaPath)));
|
||||
}
|
||||
|
||||
void RunWorkshop()
|
||||
{
|
||||
LuaCsLogger.LogMessage("Using LuaSetup.lua from the content package.");
|
||||
string luaPath = Path.Combine(Path.GetDirectoryName(luaPackage.Path), "Binary/Lua/LuaSetup.lua");
|
||||
CallLuaFunction(Lua.LoadFile(luaPath), Path.GetDirectoryName(Path.GetFullPath(luaPath)));
|
||||
}
|
||||
|
||||
void RunNone()
|
||||
{
|
||||
LuaCsLogger.LogError("LuaSetup.lua not found! Lua/LuaSetup.lua, no Lua scripts will be executed or work.", LuaCsMessageOrigin.LuaMod);
|
||||
}
|
||||
|
||||
if (Config.PreferToUseWorkshopLuaSetup)
|
||||
{
|
||||
if (luaPackage != null) { RunWorkshop(); }
|
||||
else if (File.Exists(LuaSetupFile)) { RunLocal(); }
|
||||
else { RunNone(); }
|
||||
}
|
||||
else
|
||||
{
|
||||
if (File.Exists(LuaSetupFile)) { RunLocal(); }
|
||||
else if (luaPackage != null) { RunWorkshop(); }
|
||||
else { RunNone(); }
|
||||
}
|
||||
|
||||
#if SERVER
|
||||
GameMain.Server.ServerSettings.LoadClientPermissions();
|
||||
#endif
|
||||
|
||||
executionNumber++;
|
||||
public void Dispose()
|
||||
{
|
||||
// TODO release managed resources here
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ using System.Linq;
|
||||
using System.Net;
|
||||
using System.Reflection;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.LuaCs;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -262,7 +263,8 @@ namespace Barotrauma
|
||||
|
||||
private static Type[] LoadDocTypes(XElement typesElem)
|
||||
{
|
||||
var result = new List<Type>();
|
||||
throw new NotImplementedException();
|
||||
/*var result = new List<Type>();
|
||||
var loadedTypes = LuaCsSetup.AssemblyManager
|
||||
.GetAllTypesInLoadedAssemblies()
|
||||
.ToImmutableHashSet();
|
||||
@@ -279,7 +281,7 @@ namespace Barotrauma
|
||||
result.AddRange(typesFound);
|
||||
}
|
||||
|
||||
return result.ToArray();
|
||||
return result.ToArray();*/
|
||||
}
|
||||
|
||||
private static IEnumerable<XElement> SaveDocTypes(IEnumerable<Type> types)
|
||||
|
||||
@@ -1,341 +1,534 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Xml.Serialization;
|
||||
using Barotrauma;
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.LuaCs.Data;
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.Xna.Framework;
|
||||
using OneOf;
|
||||
using Platform = Barotrauma.LuaCs.Data.Platform;
|
||||
|
||||
namespace Barotrauma;
|
||||
|
||||
public static class ModUtils
|
||||
namespace Barotrauma.LuaCs
|
||||
{
|
||||
#region LOGGING
|
||||
|
||||
public static class Logging
|
||||
public static class ModUtils
|
||||
{
|
||||
public static void PrintMessage(string s)
|
||||
public static class Environment
|
||||
{
|
||||
#if SERVER
|
||||
LuaCsLogger.LogMessage($"[Server] {s}");
|
||||
internal static void SetCurrentThreadAsMain() => MainThreadId = Thread.CurrentThread.ManagedThreadId;
|
||||
public static int MainThreadId { get; private set; } = Int32.MinValue;
|
||||
public static bool IsMainThread
|
||||
{
|
||||
get
|
||||
{
|
||||
if (MainThreadId == Int32.MinValue)
|
||||
throw new ArgumentNullException("MainThread ID not set.");
|
||||
return Thread.CurrentThread.ManagedThreadId == MainThreadId;
|
||||
}
|
||||
}
|
||||
|
||||
public static readonly Platform CurrentPlatform =
|
||||
#if WINDOWS
|
||||
Platform.Windows;
|
||||
#elif MACOS
|
||||
Platform.MacOS;
|
||||
#elif LINUX
|
||||
Platform.Linux;
|
||||
#else
|
||||
LuaCsLogger.LogMessage($"[Client] {s}");
|
||||
Platform.Linux;
|
||||
#endif
|
||||
}
|
||||
|
||||
public static void PrintWarning(string s)
|
||||
{
|
||||
#if SERVER
|
||||
LuaCsLogger.Log($"[Server] {s}", Color.Yellow);
|
||||
public static readonly Target CurrentTarget =
|
||||
#if CLIENT
|
||||
Target.Client;
|
||||
#elif SERVER
|
||||
Target.Server;
|
||||
#else
|
||||
LuaCsLogger.Log($"[Client] {s}", Color.Yellow);
|
||||
Target.Server;
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
public static void PrintError(string s)
|
||||
#region LOGGING
|
||||
|
||||
public static class Logging
|
||||
{
|
||||
public static void PrintMessage(string s)
|
||||
{
|
||||
#if SERVER
|
||||
LuaCsLogger.LogError($"[Server] {s}");
|
||||
LuaCsLogger.LogMessage($"[Server] {s}");
|
||||
#else
|
||||
LuaCsLogger.LogError($"[Client] {s}");
|
||||
LuaCsLogger.LogMessage($"[Client] {s}");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region FILE_IO
|
||||
|
||||
// ReSharper disable once InconsistentNaming
|
||||
public static class IO
|
||||
{
|
||||
public static IEnumerable<string> FindAllFilesInDirectory(string folder, string pattern,
|
||||
SearchOption option)
|
||||
{
|
||||
try
|
||||
{
|
||||
return Directory.GetFiles(folder, pattern, option);
|
||||
}
|
||||
catch (DirectoryNotFoundException e)
|
||||
|
||||
public static void PrintWarning(string s)
|
||||
{
|
||||
return new string[] { };
|
||||
#if SERVER
|
||||
LuaCsLogger.Log($"[Server] {s}", Color.Yellow);
|
||||
#else
|
||||
LuaCsLogger.Log($"[Client] {s}", Color.Yellow);
|
||||
#endif
|
||||
}
|
||||
|
||||
public static void PrintError(string s)
|
||||
{
|
||||
#if SERVER
|
||||
LuaCsLogger.LogError($"[Server] {s}");
|
||||
#else
|
||||
LuaCsLogger.LogError($"[Client] {s}");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
public static string PrepareFilePathString(string filePath) =>
|
||||
PrepareFilePathString(Path.GetDirectoryName(filePath)!, Path.GetFileName(filePath));
|
||||
#endregion
|
||||
|
||||
public static string PrepareFilePathString(string path, string fileName) =>
|
||||
Path.Combine(SanitizePath(path), SanitizeFileName(fileName));
|
||||
#region FILE_IO
|
||||
|
||||
public static string SanitizeFileName(string fileName)
|
||||
// ReSharper disable once InconsistentNaming
|
||||
public static class IO
|
||||
{
|
||||
foreach (char c in Barotrauma.IO.Path.GetInvalidFileNameCharsCrossPlatform())
|
||||
fileName = fileName.Replace(c, '_');
|
||||
return fileName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sanitized path for the top-level directory for a given content package.
|
||||
/// </summary>
|
||||
/// <param name="package"></param>
|
||||
/// <returns></returns>
|
||||
public static string GetContentPackageDir(ContentPackage package)
|
||||
{
|
||||
return SanitizePath(Path.GetFullPath(package.Dir));
|
||||
}
|
||||
|
||||
public static string SanitizePath(string path)
|
||||
{
|
||||
foreach (char c in Path.GetInvalidPathChars())
|
||||
path = path.Replace(c.ToString(), "_");
|
||||
return path.CleanUpPath();
|
||||
}
|
||||
|
||||
public static IOActionResultState GetOrCreateFileText(string filePath, out string fileText, Func<string> fileDataFactory = null, bool createFile = true)
|
||||
{
|
||||
fileText = null;
|
||||
string fp = Path.GetFullPath(SanitizePath(filePath));
|
||||
|
||||
IOActionResultState ioActionResultState = IOActionResultState.Success;
|
||||
if (createFile)
|
||||
{
|
||||
ioActionResultState = CreateFilePath(SanitizePath(filePath), out fp, fileDataFactory);
|
||||
}
|
||||
else if (!File.Exists(fp))
|
||||
{
|
||||
return IOActionResultState.FileNotFound;
|
||||
}
|
||||
|
||||
if (ioActionResultState == IOActionResultState.Success)
|
||||
public static IEnumerable<string> FindAllFilesInDirectory(string folder, string pattern,
|
||||
SearchOption option)
|
||||
{
|
||||
try
|
||||
{
|
||||
fileText = File.ReadAllText(fp!);
|
||||
return IOActionResultState.Success;
|
||||
return Directory.GetFiles(folder, pattern, option);
|
||||
}
|
||||
catch (ArgumentNullException ane)
|
||||
catch (DirectoryNotFoundException e)
|
||||
{
|
||||
ModUtils.Logging.PrintError($"ModUtils::CreateFilePath() | Exception: An argument is null. path: {fp ?? "null"} | Exception Details: {ane.Message}");
|
||||
return IOActionResultState.FilePathNull;
|
||||
}
|
||||
catch (ArgumentException ae)
|
||||
{
|
||||
ModUtils.Logging.PrintError($"ModUtils::CreateFilePath() | Exception: An argument is invalid. path: {fp ?? "null"} | Exception Details: {ae.Message}");
|
||||
return IOActionResultState.FilePathInvalid;
|
||||
}
|
||||
catch (DirectoryNotFoundException dnfe)
|
||||
{
|
||||
ModUtils.Logging.PrintError($"ModUtils::CreateFilePath() | Exception: Cannot find directory. path: {fp ?? "null"} | Exception Details: {dnfe.Message}");
|
||||
return IOActionResultState.DirectoryMissing;
|
||||
}
|
||||
catch (PathTooLongException ptle)
|
||||
{
|
||||
ModUtils.Logging.PrintError($"ModUtils::CreateFilePath() | Exception: path length is over 200 characters. path: {fp ?? "null"} | Exception Details: {ptle.Message}");
|
||||
return IOActionResultState.PathTooLong;
|
||||
}
|
||||
catch (NotSupportedException nse)
|
||||
{
|
||||
ModUtils.Logging.PrintError($"ModUtils::CreateFilePath() | Exception: Operation not supported on your platform/environment (permissions?). path: {fp ?? "null"} | Exception Details: {nse.Message}");
|
||||
return IOActionResultState.InvalidOperation;
|
||||
}
|
||||
catch (IOException ioe)
|
||||
{
|
||||
ModUtils.Logging.PrintError($"ModUtils::CreateFilePath() | Exception: IO tasks failed (Operation not supported). path: {fp ?? "null"} | Exception Details: {ioe.Message}");
|
||||
return IOActionResultState.IOFailure;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ModUtils.Logging.PrintError($"ModUtils::CreateFilePath() | Exception: Unknown/Other Exception. path: {fp ?? "null"} | ExceptionMessage: {e.Message}");
|
||||
return IOActionResultState.UnknownError;
|
||||
return new string[] { };
|
||||
}
|
||||
}
|
||||
|
||||
return ioActionResultState;
|
||||
}
|
||||
public static string PrepareFilePathString(string filePath) =>
|
||||
PrepareFilePathString(Path.GetDirectoryName(filePath)!, Path.GetFileName(filePath));
|
||||
|
||||
public static IOActionResultState CreateFilePath(string filePath, out string formattedFilePath, Func<string> fileDataFactory = null)
|
||||
{
|
||||
string file = Path.GetFileName(filePath);
|
||||
string path = Path.GetDirectoryName(filePath)!;
|
||||
public static string PrepareFilePathString(string path, string fileName) =>
|
||||
Path.Combine(SanitizePath(path), SanitizeFileName(fileName));
|
||||
|
||||
formattedFilePath = IO.PrepareFilePathString(path, file);
|
||||
try
|
||||
public static string SanitizeFileName(string fileName)
|
||||
{
|
||||
if (!Directory.Exists(path))
|
||||
Directory.CreateDirectory(path);
|
||||
if (!File.Exists(formattedFilePath))
|
||||
File.WriteAllText(formattedFilePath, fileDataFactory is null ? "" : fileDataFactory.Invoke());
|
||||
return IOActionResultState.Success;
|
||||
}
|
||||
catch (ArgumentNullException ane)
|
||||
{
|
||||
ModUtils.Logging.PrintError($"ModUtils::CreateFilePath() | Exception: An argument is null. path: {formattedFilePath ?? "null"} | Exception Details: {ane.Message}");
|
||||
return IOActionResultState.FilePathNull;
|
||||
}
|
||||
catch (ArgumentException ae)
|
||||
{
|
||||
ModUtils.Logging.PrintError($"ModUtils::CreateFilePath() | Exception: An argument is invalid. path: {formattedFilePath ?? "null"} | Exception Details: {ae.Message}");
|
||||
return IOActionResultState.FilePathInvalid;
|
||||
}
|
||||
catch (DirectoryNotFoundException dnfe)
|
||||
{
|
||||
ModUtils.Logging.PrintError($"ModUtils::CreateFilePath() | Exception: Cannot find directory. path: {path ?? "null"} | Exception Details: {dnfe.Message}");
|
||||
return IOActionResultState.DirectoryMissing;
|
||||
}
|
||||
catch (PathTooLongException ptle)
|
||||
{
|
||||
ModUtils.Logging.PrintError($"ModUtils::CreateFilePath() | Exception: path length is over 200 characters. path: {formattedFilePath ?? "null"} | Exception Details: {ptle.Message}");
|
||||
return IOActionResultState.PathTooLong;
|
||||
}
|
||||
catch (NotSupportedException nse)
|
||||
{
|
||||
ModUtils.Logging.PrintError($"ModUtils::CreateFilePath() | Exception: Operation not supported on your platform/environment (permissions?). path: {formattedFilePath ?? "null"} | Exception Details: {nse.Message}");
|
||||
return IOActionResultState.InvalidOperation;
|
||||
}
|
||||
catch (IOException ioe)
|
||||
{
|
||||
ModUtils.Logging.PrintError($"ModUtils::CreateFilePath() | Exception: IO tasks failed (Operation not supported). path: {formattedFilePath ?? "null"} | Exception Details: {ioe.Message}");
|
||||
return IOActionResultState.IOFailure;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ModUtils.Logging.PrintError($"ModUtils::CreateFilePath() | Exception: Unknown/Other Exception. path: {path ?? "null"} | Exception Details: {e.Message}");
|
||||
return IOActionResultState.UnknownError;
|
||||
}
|
||||
}
|
||||
|
||||
public static IOActionResultState WriteFileText(string filePath, string fileText)
|
||||
{
|
||||
IOActionResultState ioActionResultState = CreateFilePath(filePath, out var fp);
|
||||
if (ioActionResultState == IOActionResultState.Success)
|
||||
{
|
||||
try
|
||||
{
|
||||
File.WriteAllText(fp!, fileText);
|
||||
return IOActionResultState.Success;
|
||||
}
|
||||
catch (ArgumentNullException ane)
|
||||
{
|
||||
ModUtils.Logging.PrintError($"ModUtils::WriteFileText() | Exception: An argument is null. path: {fp ?? "null"} | Exception Details: {ane.Message}");
|
||||
return IOActionResultState.FilePathNull;
|
||||
}
|
||||
catch (ArgumentException ae)
|
||||
{
|
||||
ModUtils.Logging.PrintError($"ModUtils::WriteFileText() | Exception: An argument is invalid. path: {fp ?? "null"} | Exception Details: {ae.Message}");
|
||||
return IOActionResultState.FilePathInvalid;
|
||||
}
|
||||
catch (DirectoryNotFoundException dnfe)
|
||||
{
|
||||
ModUtils.Logging.PrintError($"ModUtils::WriteFileText() | Exception: Cannot find directory. path: {fp ?? "null"} | Exception Details: {dnfe.Message}");
|
||||
return IOActionResultState.DirectoryMissing;
|
||||
}
|
||||
catch (PathTooLongException ptle)
|
||||
{
|
||||
ModUtils.Logging.PrintError($"ModUtils::WriteFileText() | Exception: path length is over 200 characters. path: {fp ?? "null"} | Exception Details: {ptle.Message}");
|
||||
return IOActionResultState.PathTooLong;
|
||||
}
|
||||
catch (NotSupportedException nse)
|
||||
{
|
||||
ModUtils.Logging.PrintError($"ModUtils::WriteFileText() | Exception: Operation not supported on your platform/environment (permissions?). path: {fp ?? "null"} | Exception Details: {nse.Message}");
|
||||
return IOActionResultState.InvalidOperation;
|
||||
}
|
||||
catch (IOException ioe)
|
||||
{
|
||||
ModUtils.Logging.PrintError($"ModUtils::WriteFileText() | Exception: IO tasks failed (Operation not supported). path: {fp ?? "null"} | Exception Details: {ioe.Message}");
|
||||
return IOActionResultState.IOFailure;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ModUtils.Logging.PrintError($"ModUtils::WriteFileText() | Exception: Unknown/Other Exception. path: {fp ?? "null"} | ExceptionMessage: {e.Message}");
|
||||
return IOActionResultState.UnknownError;
|
||||
}
|
||||
foreach (char c in Barotrauma.IO.Path.GetInvalidFileNameCharsCrossPlatform())
|
||||
fileName = fileName.Replace(c, '_');
|
||||
return fileName;
|
||||
}
|
||||
|
||||
return ioActionResultState;
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets the sanitized path for the top-level directory for a given content package.
|
||||
/// </summary>
|
||||
/// <param name="package"></param>
|
||||
/// <returns></returns>
|
||||
public static string GetContentPackageDir(ContentPackage package)
|
||||
{
|
||||
return SanitizePath(Path.GetFullPath(package.Dir));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="instance"></param>
|
||||
/// <param name="filepath"></param>
|
||||
/// <param name="typeFactory"></param>
|
||||
/// <param name="createFile"></param>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns></returns>
|
||||
public static bool LoadOrCreateTypeXml<T>(out T instance,
|
||||
string filepath, Func<T> typeFactory = null, bool createFile = true) where T : class, new()
|
||||
{
|
||||
instance = null;
|
||||
filepath = filepath.CleanUpPath();
|
||||
if (IOActionResultState.Success == GetOrCreateFileText(
|
||||
filepath, out string fileText, typeFactory is not null ? () =>
|
||||
public static string SanitizePath(string path)
|
||||
{
|
||||
foreach (char c in Path.GetInvalidPathChars())
|
||||
path = path.Replace(c.ToString(), "_");
|
||||
return path.CleanUpPath();
|
||||
}
|
||||
|
||||
public static IOActionResultState GetOrCreateFileText(string filePath, out string fileText,
|
||||
Func<string> fileDataFactory = null, bool createFile = true)
|
||||
{
|
||||
fileText = null;
|
||||
string fp = Path.GetFullPath(SanitizePath(filePath));
|
||||
|
||||
IOActionResultState ioActionResultState = IOActionResultState.Success;
|
||||
if (createFile)
|
||||
{
|
||||
ioActionResultState = CreateFilePath(SanitizePath(filePath), out fp, fileDataFactory);
|
||||
}
|
||||
else if (!File.Exists(fp))
|
||||
{
|
||||
return IOActionResultState.FileNotFound;
|
||||
}
|
||||
|
||||
if (ioActionResultState == IOActionResultState.Success)
|
||||
{
|
||||
try
|
||||
{
|
||||
using StringWriter sw = new StringWriter();
|
||||
T t = typeFactory?.Invoke();
|
||||
if (t is not null)
|
||||
{
|
||||
XmlSerializer s = new XmlSerializer(typeof(T));
|
||||
s.Serialize(sw, t);
|
||||
return sw.ToString();
|
||||
}
|
||||
return "";
|
||||
} : null, createFile))
|
||||
fileText = File.ReadAllText(fp!);
|
||||
return IOActionResultState.Success;
|
||||
}
|
||||
catch (ArgumentNullException ane)
|
||||
{
|
||||
ModUtils.Logging.PrintError(
|
||||
$"ModUtils::CreateFilePath() | Exception: An argument is null. path: {fp ?? "null"} | Exception Details: {ane.Message}");
|
||||
return IOActionResultState.FilePathNull;
|
||||
}
|
||||
catch (ArgumentException ae)
|
||||
{
|
||||
ModUtils.Logging.PrintError(
|
||||
$"ModUtils::CreateFilePath() | Exception: An argument is invalid. path: {fp ?? "null"} | Exception Details: {ae.Message}");
|
||||
return IOActionResultState.FilePathInvalid;
|
||||
}
|
||||
catch (DirectoryNotFoundException dnfe)
|
||||
{
|
||||
ModUtils.Logging.PrintError(
|
||||
$"ModUtils::CreateFilePath() | Exception: Cannot find directory. path: {fp ?? "null"} | Exception Details: {dnfe.Message}");
|
||||
return IOActionResultState.DirectoryMissing;
|
||||
}
|
||||
catch (PathTooLongException ptle)
|
||||
{
|
||||
ModUtils.Logging.PrintError(
|
||||
$"ModUtils::CreateFilePath() | Exception: path length is over 200 characters. path: {fp ?? "null"} | Exception Details: {ptle.Message}");
|
||||
return IOActionResultState.PathTooLong;
|
||||
}
|
||||
catch (NotSupportedException nse)
|
||||
{
|
||||
ModUtils.Logging.PrintError(
|
||||
$"ModUtils::CreateFilePath() | Exception: Operation not supported on your platform/environment (permissions?). path: {fp ?? "null"} | Exception Details: {nse.Message}");
|
||||
return IOActionResultState.InvalidOperation;
|
||||
}
|
||||
catch (IOException ioe)
|
||||
{
|
||||
ModUtils.Logging.PrintError(
|
||||
$"ModUtils::CreateFilePath() | Exception: IO tasks failed (Operation not supported). path: {fp ?? "null"} | Exception Details: {ioe.Message}");
|
||||
return IOActionResultState.IOFailure;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ModUtils.Logging.PrintError(
|
||||
$"ModUtils::CreateFilePath() | Exception: Unknown/Other Exception. path: {fp ?? "null"} | ExceptionMessage: {e.Message}");
|
||||
return IOActionResultState.UnknownError;
|
||||
}
|
||||
}
|
||||
|
||||
return ioActionResultState;
|
||||
}
|
||||
|
||||
public static IOActionResultState CreateFilePath(string filePath, out string formattedFilePath,
|
||||
Func<string> fileDataFactory = null)
|
||||
{
|
||||
XmlSerializer s = new XmlSerializer(typeof(T));
|
||||
string file = Path.GetFileName(filePath);
|
||||
string path = Path.GetDirectoryName(filePath)!;
|
||||
|
||||
formattedFilePath = IO.PrepareFilePathString(path, file);
|
||||
try
|
||||
{
|
||||
using TextReader tr = new StringReader(fileText);
|
||||
instance = (T)s.Deserialize(tr);
|
||||
return true;
|
||||
if (!Directory.Exists(path))
|
||||
Directory.CreateDirectory(path);
|
||||
if (!File.Exists(formattedFilePath))
|
||||
File.WriteAllText(formattedFilePath, fileDataFactory is null ? "" : fileDataFactory.Invoke());
|
||||
return IOActionResultState.Success;
|
||||
}
|
||||
catch(InvalidOperationException ioe)
|
||||
catch (ArgumentNullException ane)
|
||||
{
|
||||
ModUtils.Logging.PrintError($"Error while parsing type data for {typeof(T)}.");
|
||||
#if DEBUG
|
||||
ModUtils.Logging.PrintError($"Exception: {ioe.Message}. Details: {ioe.InnerException?.Message}");
|
||||
#endif
|
||||
instance = null;
|
||||
return false;
|
||||
ModUtils.Logging.PrintError(
|
||||
$"ModUtils::CreateFilePath() | Exception: An argument is null. path: {formattedFilePath ?? "null"} | Exception Details: {ane.Message}");
|
||||
return IOActionResultState.FilePathNull;
|
||||
}
|
||||
catch (ArgumentException ae)
|
||||
{
|
||||
ModUtils.Logging.PrintError(
|
||||
$"ModUtils::CreateFilePath() | Exception: An argument is invalid. path: {formattedFilePath ?? "null"} | Exception Details: {ae.Message}");
|
||||
return IOActionResultState.FilePathInvalid;
|
||||
}
|
||||
catch (DirectoryNotFoundException dnfe)
|
||||
{
|
||||
ModUtils.Logging.PrintError(
|
||||
$"ModUtils::CreateFilePath() | Exception: Cannot find directory. path: {path ?? "null"} | Exception Details: {dnfe.Message}");
|
||||
return IOActionResultState.DirectoryMissing;
|
||||
}
|
||||
catch (PathTooLongException ptle)
|
||||
{
|
||||
ModUtils.Logging.PrintError(
|
||||
$"ModUtils::CreateFilePath() | Exception: path length is over 200 characters. path: {formattedFilePath ?? "null"} | Exception Details: {ptle.Message}");
|
||||
return IOActionResultState.PathTooLong;
|
||||
}
|
||||
catch (NotSupportedException nse)
|
||||
{
|
||||
ModUtils.Logging.PrintError(
|
||||
$"ModUtils::CreateFilePath() | Exception: Operation not supported on your platform/environment (permissions?). path: {formattedFilePath ?? "null"} | Exception Details: {nse.Message}");
|
||||
return IOActionResultState.InvalidOperation;
|
||||
}
|
||||
catch (IOException ioe)
|
||||
{
|
||||
ModUtils.Logging.PrintError(
|
||||
$"ModUtils::CreateFilePath() | Exception: IO tasks failed (Operation not supported). path: {formattedFilePath ?? "null"} | Exception Details: {ioe.Message}");
|
||||
return IOActionResultState.IOFailure;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ModUtils.Logging.PrintError(
|
||||
$"ModUtils::CreateFilePath() | Exception: Unknown/Other Exception. path: {path ?? "null"} | Exception Details: {e.Message}");
|
||||
return IOActionResultState.UnknownError;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
public static IOActionResultState WriteFileText(string filePath, string fileText)
|
||||
{
|
||||
IOActionResultState ioActionResultState = CreateFilePath(filePath, out var fp);
|
||||
if (ioActionResultState == IOActionResultState.Success)
|
||||
{
|
||||
try
|
||||
{
|
||||
File.WriteAllText(fp!, fileText);
|
||||
return IOActionResultState.Success;
|
||||
}
|
||||
catch (ArgumentNullException ane)
|
||||
{
|
||||
ModUtils.Logging.PrintError(
|
||||
$"ModUtils::WriteFileText() | Exception: An argument is null. path: {fp ?? "null"} | Exception Details: {ane.Message}");
|
||||
return IOActionResultState.FilePathNull;
|
||||
}
|
||||
catch (ArgumentException ae)
|
||||
{
|
||||
ModUtils.Logging.PrintError(
|
||||
$"ModUtils::WriteFileText() | Exception: An argument is invalid. path: {fp ?? "null"} | Exception Details: {ae.Message}");
|
||||
return IOActionResultState.FilePathInvalid;
|
||||
}
|
||||
catch (DirectoryNotFoundException dnfe)
|
||||
{
|
||||
ModUtils.Logging.PrintError(
|
||||
$"ModUtils::WriteFileText() | Exception: Cannot find directory. path: {fp ?? "null"} | Exception Details: {dnfe.Message}");
|
||||
return IOActionResultState.DirectoryMissing;
|
||||
}
|
||||
catch (PathTooLongException ptle)
|
||||
{
|
||||
ModUtils.Logging.PrintError(
|
||||
$"ModUtils::WriteFileText() | Exception: path length is over 200 characters. path: {fp ?? "null"} | Exception Details: {ptle.Message}");
|
||||
return IOActionResultState.PathTooLong;
|
||||
}
|
||||
catch (NotSupportedException nse)
|
||||
{
|
||||
ModUtils.Logging.PrintError(
|
||||
$"ModUtils::WriteFileText() | Exception: Operation not supported on your platform/environment (permissions?). path: {fp ?? "null"} | Exception Details: {nse.Message}");
|
||||
return IOActionResultState.InvalidOperation;
|
||||
}
|
||||
catch (IOException ioe)
|
||||
{
|
||||
ModUtils.Logging.PrintError(
|
||||
$"ModUtils::WriteFileText() | Exception: IO tasks failed (Operation not supported). path: {fp ?? "null"} | Exception Details: {ioe.Message}");
|
||||
return IOActionResultState.IOFailure;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ModUtils.Logging.PrintError(
|
||||
$"ModUtils::WriteFileText() | Exception: Unknown/Other Exception. path: {fp ?? "null"} | ExceptionMessage: {e.Message}");
|
||||
return IOActionResultState.UnknownError;
|
||||
}
|
||||
}
|
||||
|
||||
return ioActionResultState;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="instance"></param>
|
||||
/// <param name="filepath"></param>
|
||||
/// <param name="typeFactory"></param>
|
||||
/// <param name="createFile"></param>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns></returns>
|
||||
public static bool LoadOrCreateTypeXml<T>(out T instance,
|
||||
string filepath, Func<T> typeFactory = null, bool createFile = true) where T : class, new()
|
||||
{
|
||||
instance = null;
|
||||
filepath = filepath.CleanUpPath();
|
||||
if (IOActionResultState.Success == GetOrCreateFileText(
|
||||
filepath, out string fileText, typeFactory is not null
|
||||
? () =>
|
||||
{
|
||||
using StringWriter sw = new StringWriter();
|
||||
T t = typeFactory?.Invoke();
|
||||
if (t is not null)
|
||||
{
|
||||
XmlSerializer s = new XmlSerializer(typeof(T));
|
||||
s.Serialize(sw, t);
|
||||
return sw.ToString();
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
: null, createFile))
|
||||
{
|
||||
XmlSerializer s = new XmlSerializer(typeof(T));
|
||||
try
|
||||
{
|
||||
using TextReader tr = new StringReader(fileText);
|
||||
instance = (T)s.Deserialize(tr);
|
||||
return true;
|
||||
}
|
||||
catch (InvalidOperationException ioe)
|
||||
{
|
||||
ModUtils.Logging.PrintError($"Error while parsing type data for {typeof(T)}.");
|
||||
#if DEBUG
|
||||
ModUtils.Logging.PrintError(
|
||||
$"Exception: {ioe.Message}. Details: {ioe.InnerException?.Message}");
|
||||
#endif
|
||||
instance = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public enum IOActionResultState
|
||||
{
|
||||
Success,
|
||||
FileNotFound,
|
||||
FilePathNull,
|
||||
FilePathInvalid,
|
||||
DirectoryMissing,
|
||||
PathTooLong,
|
||||
InvalidOperation,
|
||||
IOFailure,
|
||||
UnknownError
|
||||
}
|
||||
}
|
||||
|
||||
public enum IOActionResultState
|
||||
#endregion
|
||||
|
||||
#region GAME
|
||||
|
||||
public static class Game
|
||||
{
|
||||
Success, FileNotFound, FilePathNull, FilePathInvalid, DirectoryMissing, PathTooLong, InvalidOperation, IOFailure, UnknownError
|
||||
/// <summary>
|
||||
/// Returns whether or not there is a round running.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static bool IsRoundInProgress()
|
||||
{
|
||||
#if CLIENT
|
||||
if (Screen.Selected is not null
|
||||
&& Screen.Selected.IsEditor)
|
||||
return false;
|
||||
#endif
|
||||
return GameMain.GameSession is not null && Level.Loaded is not null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region THREADING
|
||||
|
||||
public static class Threading
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the boolean value of an integer with thread-safety via <code>Interlocked</code>.
|
||||
/// </summary>
|
||||
/// <param name="var"></param>
|
||||
/// <returns></returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool GetBool(ref int var) => Interlocked.CompareExchange(ref var, 1, 1) > 0;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void SetBool(ref int var, bool value)
|
||||
{
|
||||
if (value)
|
||||
{
|
||||
Interlocked.CompareExchange(ref var, 1, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
Interlocked.CompareExchange(ref var, 0, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets if the integer is under 1 (is zero/false) and, if so, sets the value to one/true.
|
||||
/// </summary>
|
||||
/// <param name="var"></param>
|
||||
/// <returns></returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool CheckClearAndSetBool(ref int var)
|
||||
{
|
||||
return Interlocked.CompareExchange(ref var, 1, 0) < 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets if the integer is over 0 (is one/true) and, if so, sets the value to zero/false.
|
||||
/// </summary>
|
||||
/// <param name="var"></param>
|
||||
/// <returns></returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool CheckSetAndClearBool(ref int var)
|
||||
{
|
||||
return Interlocked.CompareExchange(ref var, 0, 1) > 0;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region UTILITIES_CORE
|
||||
|
||||
public static V TryGetOrSet<K, V>(this IDictionary<K,V> dict, K key, Func<V> valueFactory) where K : IEquatable<K>
|
||||
{
|
||||
if (dict.TryGetValue(key, out var dictValue)) return dictValue;
|
||||
if (valueFactory is not null)
|
||||
dict.Add(key, valueFactory());
|
||||
else
|
||||
return default;
|
||||
return dict[key];
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region GAME
|
||||
|
||||
public static class Game
|
||||
public static class AssemblyExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns whether or not there is a round running.
|
||||
/// Gets all types in the given assembly. Handles invalid type scenarios.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static bool IsRoundInProgress()
|
||||
/// <param name="assembly">The assembly to scan</param>
|
||||
/// <returns>An enumerable collection of types.</returns>
|
||||
public static IEnumerable<Type> GetSafeTypes(this Assembly assembly)
|
||||
{
|
||||
#if CLIENT
|
||||
if (Screen.Selected is not null
|
||||
&& Screen.Selected.IsEditor)
|
||||
return false;
|
||||
#endif
|
||||
return GameMain.GameSession is not null && Level.Loaded is not null;
|
||||
// Based on https://github.com/Qkrisi/ktanemodkit/blob/master/Assets/Scripts/ReflectionHelper.cs#L53-L67
|
||||
|
||||
try
|
||||
{
|
||||
return assembly.GetTypes();
|
||||
}
|
||||
catch (ReflectionTypeLoadException re)
|
||||
{
|
||||
try
|
||||
{
|
||||
return re.Types.Where(x => x != null)!;
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
return new List<Type>();
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return new List<Type>();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
#region ExceptionData
|
||||
|
||||
namespace FluentResults.LuaCs
|
||||
{
|
||||
public static class MetadataType
|
||||
{
|
||||
public static string ExceptionDetails = nameof(ExceptionDetails);
|
||||
public static string ExceptionObject = nameof(ExceptionObject);
|
||||
public static string RootObject = nameof(RootObject);
|
||||
public static string Sources = nameof(Sources);
|
||||
public static string StackTrace = nameof(StackTrace);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Barotrauma.LuaCs.Configuration;
|
||||
using System;
|
||||
using Barotrauma.LuaCs.Configuration;
|
||||
using Barotrauma.LuaCs.Data;
|
||||
using Barotrauma.LuaCs.Networking;
|
||||
using Barotrauma.Networking;
|
||||
@@ -7,10 +8,6 @@ namespace Barotrauma.LuaCs.Networking;
|
||||
|
||||
public interface INetVar : IVarId
|
||||
{
|
||||
/// <summary>
|
||||
/// Synchronized network id, uninitialized if value is zero/0. Used by Networking service.
|
||||
/// </summary>
|
||||
ushort NetId { get; }
|
||||
/// <summary>
|
||||
/// Synchronization type
|
||||
/// </summary>
|
||||
@@ -19,7 +16,12 @@ public interface INetVar : IVarId
|
||||
/// Permissions needed by clients to send net-events or receive net messages.
|
||||
/// </summary>
|
||||
ClientPermissions WritePermissions { get; }
|
||||
|
||||
void ReadNetMessage(INetReadMessage message);
|
||||
void WriteNetMessage(INetWriteMessage message);
|
||||
void Initialize(ushort netId, NetSync syncMode, ClientPermissions writePermissions);
|
||||
}
|
||||
|
||||
public enum NetSync
|
||||
{
|
||||
None, TwoWay, ServerAuthority, ClientOneWay
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Barotrauma.LuaCs.Networking;
|
||||
public interface INetWriteMessage
|
||||
{
|
||||
internal IWriteMessage Message { get; }
|
||||
internal void SetMessage(IWriteMessage msg);
|
||||
internal INetWriteMessage SetMessage(IWriteMessage msg);
|
||||
|
||||
void WriteBoolean(bool val) => Message.WriteBoolean(val);
|
||||
|
||||
@@ -84,7 +84,7 @@ public interface INetWriteMessage
|
||||
public interface INetReadMessage
|
||||
{
|
||||
internal IReadMessage Message { get; }
|
||||
internal void SetMessage(IReadMessage msg);
|
||||
internal INetReadMessage SetMessage(IReadMessage msg);
|
||||
|
||||
bool ReadBoolean() => Message.ReadBoolean();
|
||||
void ReadPadBits() => Message.ReadPadBits();
|
||||
@@ -131,20 +131,30 @@ public class NetWriteMessage : INetWriteMessage
|
||||
|
||||
IWriteMessage INetWriteMessage.Message => Message;
|
||||
|
||||
void INetWriteMessage.SetMessage(IWriteMessage msg)
|
||||
INetWriteMessage INetWriteMessage.SetMessage(IWriteMessage msg)
|
||||
{
|
||||
Message = msg;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
internal static class NetHelperExtensions
|
||||
{
|
||||
internal static INetWriteMessage ToNetWriteMessage(this IWriteMessage msg) =>
|
||||
((INetWriteMessage)new NetWriteMessage()).SetMessage(msg);
|
||||
internal static INetReadMessage ToNetReadMessage(this IReadMessage msg) =>
|
||||
((INetReadMessage)new NetReadMessage()).SetMessage(msg);
|
||||
}
|
||||
|
||||
public class NetReadMessage : INetReadMessage
|
||||
{
|
||||
private IReadMessage Message { get; set; }
|
||||
IReadMessage INetReadMessage.Message => Message;
|
||||
|
||||
void INetReadMessage.SetMessage(IReadMessage msg)
|
||||
INetReadMessage INetReadMessage.SetMessage(IReadMessage msg)
|
||||
{
|
||||
Message = msg;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Barotrauma;
|
||||
|
||||
public interface IAssemblyPlugin : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Called on plugin normal, use this for basic/core loading that does not rely on any other modded content.
|
||||
/// </summary>
|
||||
void Initialize();
|
||||
|
||||
/// <summary>
|
||||
/// Called once all plugins have been loaded. if you have integrations with any other mod, put that code here.
|
||||
/// </summary>
|
||||
void OnLoadCompleted();
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Called before Barotrauma initializes vanilla content. WARNING: This method may be called before Initialize()!
|
||||
/// </summary>
|
||||
void PreInitPatching();
|
||||
}
|
||||
@@ -8,6 +8,8 @@ using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.Loader;
|
||||
using System.Threading;
|
||||
using FluentResults;
|
||||
using FluentResults.LuaCs;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
|
||||
@@ -25,7 +27,8 @@ namespace Barotrauma.LuaCs.Services;
|
||||
/// Provides functionality for the loading, unloading and management of plugins implementing IAssemblyPlugin.
|
||||
/// All plugins are loaded into their own AssemblyLoadContext along with their dependencies.
|
||||
/// </summary>
|
||||
public class AssemblyManager : IAssemblyManagementService
|
||||
[Obsolete]
|
||||
public class AssemblyManager : IAssemblyManagementService, IPluginManagementService
|
||||
{
|
||||
#region ExternalAPI
|
||||
|
||||
@@ -275,6 +278,11 @@ public class AssemblyManager : IAssemblyManagementService
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsAssemblyLoadedGlobal(string friendlyName)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region InternalAPI
|
||||
@@ -740,41 +748,12 @@ public class AssemblyManager : IAssemblyManagementService
|
||||
TryBeginDispose();
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
public FluentResults.Result Reset()
|
||||
{
|
||||
TryBeginDispose();
|
||||
return TryBeginDispose() ? FluentResults.Result.Ok()
|
||||
: FluentResults.Result.Fail(new Error($"{nameof(AssemblyManager)}: failed to Reset service.")
|
||||
.WithMetadata(MetadataType.ExceptionObject, this));
|
||||
}
|
||||
}
|
||||
|
||||
public static class AssemblyExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets all types in the given assembly. Handles invalid type scenarios.
|
||||
/// </summary>
|
||||
/// <param name="assembly">The assembly to scan</param>
|
||||
/// <returns>An enumerable collection of types.</returns>
|
||||
public static IEnumerable<Type> GetSafeTypes(this Assembly assembly)
|
||||
{
|
||||
// Based on https://github.com/Qkrisi/ktanemodkit/blob/master/Assets/Scripts/ReflectionHelper.cs#L53-L67
|
||||
|
||||
try
|
||||
{
|
||||
return assembly.GetTypes();
|
||||
}
|
||||
catch (ReflectionTypeLoadException re)
|
||||
{
|
||||
try
|
||||
{
|
||||
return re.Types.Where(x => x != null)!;
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
return new List<Type>();
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return new List<Type>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
|
||||
namespace Barotrauma.LuaCs.Services.Compatibility;
|
||||
|
||||
public interface ILuaCsHook : ILuaCsShim
|
||||
{
|
||||
[Obsolete("ACsMod is deprecated. Use ILuaEventService.Add() instead.")]
|
||||
void Add(string eventName, string identifier, LuaCsFunc callback, ACsMod mod = null);
|
||||
[Obsolete("ACsMod is deprecated. Use ILuaEventService.Add() instead.")]
|
||||
void Add(string eventName, LuaCsFunc callback, ACsMod mod = null);
|
||||
bool Exists(string eventName, string identifier);
|
||||
[Obsolete("Only Lua subscribers will receive events from call. Use ILuaEventService.Add() instead.")]
|
||||
T Call<T>(string eventName, params object[] args);
|
||||
object Call(string eventName, params object[] args);
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Barotrauma.LuaCs.Services.Compatibility;
|
||||
|
||||
public interface ILuaCsLogger : ILuaCsShim
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Barotrauma.LuaCs.Services.Compatibility;
|
||||
|
||||
internal partial interface ILuaCsNetworking : ILuaCsShim
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Barotrauma.LuaCs.Services.Compatibility;
|
||||
|
||||
public interface ILuaCsShim
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Barotrauma.LuaCs.Services.Compatibility;
|
||||
|
||||
public interface ILuaCsUtility : ILuaCsShim
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,376 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Collections.Specialized;
|
||||
using System.Dynamic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.LuaCs.Events;
|
||||
using Barotrauma.LuaCs.Services.Compatibility;
|
||||
using Barotrauma.LuaCs.Services.Safe;
|
||||
using Dynamitey;
|
||||
using FluentResults;
|
||||
using FluentResults.LuaCs;
|
||||
using HarmonyLib;
|
||||
using ImpromptuInterface;
|
||||
using OneOf;
|
||||
|
||||
namespace Barotrauma.LuaCs.Services;
|
||||
|
||||
public class EventService : IEventService, IEventAssemblyContextUnloading
|
||||
{
|
||||
private readonly record struct TypeStringKey : IEqualityComparer<TypeStringKey>, IEquatable<TypeStringKey>
|
||||
{
|
||||
public Type Type { get; init; }
|
||||
public string TypeName { get; init; }
|
||||
public readonly int HashCode;
|
||||
|
||||
public TypeStringKey(Type type)
|
||||
{
|
||||
Type = type ?? throw new ArgumentNullException(nameof(type));
|
||||
TypeName = type.Name;
|
||||
HashCode = TypeName.GetHashCode();
|
||||
}
|
||||
|
||||
public TypeStringKey(string typeName)
|
||||
{
|
||||
Type = null;
|
||||
TypeName = typeName ?? throw new ArgumentNullException(nameof(typeName));
|
||||
HashCode = TypeName.GetHashCode();
|
||||
}
|
||||
|
||||
public bool Equals(TypeStringKey x, TypeStringKey y)
|
||||
{
|
||||
if (x.Type is not null && y.Type is not null)
|
||||
return x.Type == y.Type;
|
||||
return x.TypeName == y.TypeName;
|
||||
}
|
||||
|
||||
public int GetHashCode(TypeStringKey obj)
|
||||
{
|
||||
return obj.HashCode;
|
||||
}
|
||||
|
||||
public static implicit operator TypeStringKey(Type type) => new(type);
|
||||
public static implicit operator TypeStringKey(string typeName) => new(typeName);
|
||||
}
|
||||
/// <summary>
|
||||
/// <para>Contains subscriber delegates by event and identifier.</para>
|
||||
/// Structure:<br/>
|
||||
/// - Key: Type or String, TypeName == String Equality.<br/>
|
||||
/// - Value: Dictionary<br/>
|
||||
/// ---- Key: Either string identifier or subscriber instance pointer<br/>
|
||||
/// ---- Value: Subscriber delegate<br/>
|
||||
/// </summary>
|
||||
private readonly Dictionary<TypeStringKey, Dictionary<OneOf<string, IEvent>, IEvent>> _subscriptions = new();
|
||||
private readonly Dictionary<string, string> _eventTypeNameAliases = new();
|
||||
private readonly Lazy<IPluginManagementService> _pluginManagementService;
|
||||
private readonly Dictionary<TypeStringKey, Action<string, IDictionary<string, LuaCsFunc>>> _luaSubscriptionFactories = new();
|
||||
/// <summary>
|
||||
/// A collection of factories to produce subscribers from a single lua function handle. For legacy Add() API.
|
||||
/// </summary>
|
||||
private readonly Dictionary<TypeStringKey, Action<string, LuaCsFunc>> _luaLegacySubscriptionFactories = new();
|
||||
/// <summary>
|
||||
/// A collection of lua event subscribers from Add() that had neither a valid event name nor an event alias pointing to one.
|
||||
/// Only actionable via Call().
|
||||
/// </summary>
|
||||
private readonly Dictionary<string, Dictionary<string, LuaCsFunc>> _luaOrphanSubscribers = new();
|
||||
|
||||
public EventService(Lazy<IPluginManagementService> pluginManagementService)
|
||||
{
|
||||
_pluginManagementService = pluginManagementService ?? throw new ArgumentNullException(nameof(pluginManagementService));
|
||||
}
|
||||
|
||||
public bool IsDisposed { get; private set; } = false;
|
||||
|
||||
#region Compatibility
|
||||
|
||||
[Obsolete("ACsMod is deprecated. Use ILuaEventService.Add() instead.")]
|
||||
void ILuaCsHook.Add(string eventName, string identifier, LuaCsFunc callback, ACsMod mod = null)
|
||||
{
|
||||
Add(eventName, identifier, callback);
|
||||
}
|
||||
[Obsolete("ACsMod is deprecated. Use ILuaEventService.Add() instead.")]
|
||||
void ILuaCsHook.Add(string eventName, LuaCsFunc callback, ACsMod mod = null)
|
||||
{
|
||||
Add(eventName, callback);
|
||||
}
|
||||
|
||||
public bool Exists(string eventName, string identifier)
|
||||
{
|
||||
((IService)this).CheckDisposed();
|
||||
if (_subscriptions.ContainsKey(eventName) && _subscriptions[eventName].ContainsKey(identifier))
|
||||
return true;
|
||||
if (_luaOrphanSubscribers.ContainsKey(eventName))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
[Obsolete("Part of the legacy events API, only works for Lua-only custom events.")]
|
||||
public T Call<T>(string eventName, params object[] args)
|
||||
{
|
||||
((IService)this).CheckDisposed();
|
||||
if (!_luaOrphanSubscribers.TryGetValue(eventName, out var dict))
|
||||
return default;
|
||||
T returnValue = default;
|
||||
foreach (var sub in dict.Values)
|
||||
{
|
||||
try
|
||||
{
|
||||
var r = sub(args);
|
||||
if (r != default)
|
||||
returnValue = (T)r;
|
||||
}
|
||||
catch
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
[Obsolete("Part of the legacy events API, only works for Lua-only custom events.")]
|
||||
public object Call(string eventName, params object[] args) => Call<object>(eventName, args);
|
||||
|
||||
#endregion
|
||||
|
||||
public void Add(string eventName, string identifier, LuaCsFunc callback)
|
||||
{
|
||||
var eventKey = eventName;
|
||||
if (_eventTypeNameAliases.TryGetValue(eventName, out var aliasType))
|
||||
eventKey = aliasType;
|
||||
if (_luaLegacySubscriptionFactories.TryGetValue(eventKey, out var factory))
|
||||
{
|
||||
factory(identifier, callback);
|
||||
return;
|
||||
}
|
||||
_luaOrphanSubscribers.TryGetOrSet(eventName, () => new Dictionary<string, LuaCsFunc>())
|
||||
.Add(identifier.IsNullOrWhiteSpace() ? string.Empty : identifier, callback);
|
||||
}
|
||||
|
||||
public void Add(string eventName, LuaCsFunc callback)
|
||||
{
|
||||
Add(eventName, string.Empty, callback);
|
||||
}
|
||||
|
||||
public void Remove(string eventName, string identifier)
|
||||
{
|
||||
if (_luaOrphanSubscribers.TryGetValue(eventName, out var dict))
|
||||
dict.Remove(identifier);
|
||||
if (_subscriptions.TryGetValue(eventName, out var dict2))
|
||||
dict2.Remove(identifier);
|
||||
}
|
||||
|
||||
public void PublishLuaEvent(string interfaceName, LuaCsFunc runner)
|
||||
{
|
||||
((IService)this).CheckDisposed();
|
||||
if (interfaceName.IsNullOrWhiteSpace())
|
||||
return;
|
||||
if (!_subscriptions.TryGetValue(interfaceName, out var dict))
|
||||
return;
|
||||
|
||||
var type = _subscriptions
|
||||
.Select(x => x.Key)
|
||||
.FirstOrNull(x => x.Type?.Name == interfaceName)?.Type;
|
||||
|
||||
var errors = new Queue<IError>();
|
||||
foreach (var eventSub in dict.Values)
|
||||
{
|
||||
try
|
||||
{
|
||||
runner(type is null ? eventSub : Convert.ChangeType(eventSub, type)); // cast if possible
|
||||
}
|
||||
catch
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public FluentResults.Result RegisterSafeEvent<T>() where T : IEvent<T>
|
||||
{
|
||||
((IService)this).CheckDisposed();
|
||||
var type = typeof(T);
|
||||
if (_luaSubscriptionFactories.ContainsKey(type))
|
||||
return FluentResults.Result.Ok().WithReason(new Success($"The event {type.Name} is already registered."));
|
||||
try
|
||||
{
|
||||
_luaSubscriptionFactories.Add(type, (ident, funcDict) =>
|
||||
{
|
||||
var runner = T.GetLuaRunner(funcDict);
|
||||
var dict = _subscriptions.TryGetOrSet(type, () => new Dictionary<OneOf<string, IEvent>, IEvent>());
|
||||
if (!ident.IsNullOrWhiteSpace())
|
||||
dict[ident] = runner;
|
||||
else
|
||||
dict[runner] = runner;
|
||||
});
|
||||
return FluentResults.Result.Ok();
|
||||
}
|
||||
catch (NullReferenceException e)
|
||||
{
|
||||
return FluentResults.Result.Fail(new Error($"The lua runner for {type.Name} is not registered.")
|
||||
.WithMetadata(MetadataType.ExceptionObject, this)
|
||||
.WithMetadata(MetadataType.RootObject, type));
|
||||
}
|
||||
}
|
||||
|
||||
public FluentResults.Result UnregisterSafeEvent<T>() where T : IEvent<T>
|
||||
{
|
||||
((IService)this).CheckDisposed();
|
||||
_luaSubscriptionFactories.Remove(typeof(T));
|
||||
if (!_subscriptions.TryGetValue(typeof(T), out var dict))
|
||||
return FluentResults.Result.Ok();
|
||||
dict.Values.Where(value => value.IsLuaRunner()).ToImmutableArray().ForEach(Unsubscribe);
|
||||
return FluentResults.Result.Ok();
|
||||
}
|
||||
|
||||
// lua subscribe
|
||||
public void Subscribe(string interfaceName, string identifier, IDictionary<string, LuaCsFunc> callbacks)
|
||||
{
|
||||
((IService)this).CheckDisposed();
|
||||
if (_luaSubscriptionFactories.TryGetValue(interfaceName, out var subFactory))
|
||||
subFactory(identifier, callbacks);
|
||||
}
|
||||
|
||||
public FluentResults.Result SetLegacyLuaRunnerFactory<T>(Func<LuaCsFunc, T> runnerFactory) where T : IEvent<T>
|
||||
{
|
||||
var type = typeof(T);
|
||||
if (!_luaSubscriptionFactories.TryGetValue(type, out var dict))
|
||||
return FluentResults.Result.Fail(new Error($"Tried to add legacy lua factory for an event not registered for lua subscriptions."));
|
||||
|
||||
_luaLegacySubscriptionFactories[type] = (ident, func) =>
|
||||
{
|
||||
var runner = runnerFactory(func);
|
||||
_subscriptions.TryGetOrSet(type, () => new Dictionary<OneOf<string, IEvent>, IEvent>())[ident] = runner;
|
||||
};
|
||||
return FluentResults.Result.Ok();
|
||||
}
|
||||
|
||||
public void RemoveLegacyLuaRunnerFactory<T>() where T : IEvent<T>
|
||||
{
|
||||
_luaLegacySubscriptionFactories.Remove(typeof(T));
|
||||
}
|
||||
|
||||
public void SetAliasToEvent<T>(string alias) where T : IEvent<T>
|
||||
{
|
||||
if (alias.IsNullOrWhiteSpace())
|
||||
return;
|
||||
_eventTypeNameAliases[alias] = typeof(T).Name;
|
||||
}
|
||||
|
||||
public void RemoveEventAlias(string alias)
|
||||
{
|
||||
_eventTypeNameAliases.Remove(alias);
|
||||
}
|
||||
|
||||
public void RemoveAllEventAliases<T>() where T : IEvent<T>
|
||||
{
|
||||
foreach (var keys in _eventTypeNameAliases
|
||||
.Where(kvp => kvp.Value.IsNullOrWhiteSpace() || kvp.Value == typeof(T).Name)
|
||||
.Select(kvp => kvp.Key).ToImmutableArray())
|
||||
{
|
||||
_eventTypeNameAliases.Remove(keys);
|
||||
}
|
||||
}
|
||||
|
||||
public FluentResults.Result Subscribe<T>(T subscriber) where T : IEvent<T>
|
||||
{
|
||||
((IService)this).CheckDisposed();
|
||||
var eventType = typeof(T);
|
||||
var dict = _subscriptions.TryGetOrSet(eventType, () => new Dictionary<OneOf<string, IEvent>, IEvent>());
|
||||
if (dict.ContainsKey(OneOf<string, IEvent>.FromT1(subscriber)))
|
||||
{
|
||||
return FluentResults.Result.Fail(
|
||||
new Error($"The subscriber for {eventType.Name} is already registered to the event.")
|
||||
.WithMetadata(MetadataType.ExceptionObject, this)
|
||||
.WithMetadata(MetadataType.RootObject, subscriber));
|
||||
}
|
||||
dict[subscriber] = subscriber;
|
||||
return FluentResults.Result.Ok();
|
||||
}
|
||||
|
||||
public void Unsubscribe<T>(T subscriber) where T : IEvent
|
||||
{
|
||||
((IService)this).CheckDisposed();
|
||||
if (!_subscriptions.TryGetValue(typeof(T), out var dict))
|
||||
return;
|
||||
dict.Remove(OneOf<string, IEvent>.FromT1(subscriber));
|
||||
}
|
||||
|
||||
public void ClearAllEventSubscribers<T>() where T : IEvent => _subscriptions.Remove(typeof(T));
|
||||
public void ClearAllSubscribers() => _subscriptions.Clear();
|
||||
|
||||
public FluentResults.Result PublishEvent<T>(Action<T> action) where T : IEvent<T>
|
||||
{
|
||||
((IService)this).CheckDisposed();
|
||||
var eventType = typeof(T);
|
||||
if (!_subscriptions.TryGetValue(eventType, out var dict))
|
||||
{
|
||||
return FluentResults.Result.Fail(new Error($"The event {eventType.Name} is not registered.")
|
||||
.WithMetadata(MetadataType.ExceptionObject, this));
|
||||
}
|
||||
|
||||
var errors = new Queue<IError>();
|
||||
foreach (var eventSub in dict.Values)
|
||||
{
|
||||
try
|
||||
{
|
||||
action((T)eventSub);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
errors.Enqueue(new Error($"Error while executing runner for {eventType.Name} on type {eventSub.GetType().Name}.")
|
||||
.WithMetadata(MetadataType.ExceptionObject, this)
|
||||
.WithMetadata(MetadataType.RootObject, eventSub)
|
||||
.WithMetadata(MetadataType.ExceptionDetails, e.Message)
|
||||
.WithMetadata(MetadataType.StackTrace, e.StackTrace));
|
||||
}
|
||||
}
|
||||
|
||||
var result = errors.Count > 0 ? FluentResults.Result.Fail($"Errors while executing event type {eventType.Name}") : FluentResults.Result.Ok();
|
||||
while (errors.Count > 0)
|
||||
result = result.WithError(errors.Dequeue());
|
||||
return result;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
IsDisposed = true;
|
||||
_subscriptions.Clear();
|
||||
_luaSubscriptionFactories.Clear();
|
||||
_eventTypeNameAliases.Clear();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
public FluentResults.Result Reset()
|
||||
{
|
||||
((IService)this).CheckDisposed();
|
||||
_subscriptions.Clear();
|
||||
_luaSubscriptionFactories.Clear();
|
||||
_eventTypeNameAliases.Clear();
|
||||
return FluentResults.Result.Ok();
|
||||
}
|
||||
|
||||
public void OnAssemblyUnloading(WeakReference<IAssemblyLoaderService> loaderService)
|
||||
{
|
||||
if (!loaderService.TryGetTarget(out var loader))
|
||||
return;
|
||||
foreach (var assembly in loader.Assemblies)
|
||||
{
|
||||
var types = assembly.GetSafeTypes()
|
||||
.Where(t => typeof(IEvent).IsAssignableFrom(t))
|
||||
.ToImmutableArray();
|
||||
if (!types.Any())
|
||||
continue;
|
||||
foreach (var type in types)
|
||||
{
|
||||
_subscriptions.Remove(type);
|
||||
_luaSubscriptionFactories.Remove(type);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,189 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
// ReSharper disable InconsistentNaming
|
||||
|
||||
namespace Barotrauma.LuaCs.Services;
|
||||
|
||||
|
||||
public interface IAssemblyManagementService : IService
|
||||
{
|
||||
#region Public API
|
||||
|
||||
/// <summary>
|
||||
/// Called when an assembly is loaded.
|
||||
/// </summary>
|
||||
public event Action<Assembly> OnAssemblyLoaded;
|
||||
|
||||
/// <summary>
|
||||
/// Called when an assembly is marked for unloading, before unloading begins. You should use this to cleanup
|
||||
/// any references that you have to this assembly.
|
||||
/// </summary>
|
||||
public event Action<Assembly> OnAssemblyUnloading;
|
||||
|
||||
/// <summary>
|
||||
/// Called whenever an exception is thrown. First arg is a formatted message, Second arg is the Exception.
|
||||
/// </summary>
|
||||
public event Action<string, Exception> OnException;
|
||||
|
||||
/// <summary>
|
||||
/// For unloading issue debugging. Called whenever MemoryFileAssemblyContextLoader [load context] is unloaded.
|
||||
/// </summary>
|
||||
// ReSharper disable once InconsistentNaming
|
||||
public event Action<Guid> OnACLUnload;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// [DEBUG ONLY]
|
||||
/// Returns a list of the current unloading ACLs.
|
||||
/// </summary>
|
||||
// ReSharper disable once InconsistentNaming
|
||||
public ImmutableList<WeakReference<MemoryFileAssemblyContextLoader>> StillUnloadingACLs { get; }
|
||||
|
||||
// ReSharper disable once MemberCanBePrivate.Global
|
||||
/// <summary>
|
||||
/// Checks if there are any AssemblyLoadContexts still in the process of unloading.
|
||||
/// </summary>
|
||||
public bool IsCurrentlyUnloading { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Allows iteration over all non-interface types in all loaded assemblies in the AsmMgr that are assignable to the given type (IsAssignableFrom).
|
||||
/// Warning: care should be used when using this method in hot paths as performance may be affected.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type to compare against</typeparam>
|
||||
/// <param name="rebuildList">Forces caches to clear and for the lists of types to be rebuilt.</param>
|
||||
/// <returns>An Enumerator for matching types.</returns>
|
||||
public IEnumerable<Type> GetSubTypesInLoadedAssemblies<T>(bool rebuildList);
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get types assignable to type from the ACL given the Guid.
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <param name="types"></param>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns>Operation success.</returns>
|
||||
public bool TryGetSubTypesFromACL<T>(Guid id, out IEnumerable<Type> types);
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get types from the ACL given the Guid.
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <param name="types"></param>
|
||||
/// <returns></returns>
|
||||
public bool TryGetSubTypesFromACL(Guid id, out IEnumerable<Type> types);
|
||||
|
||||
/// <summary>
|
||||
/// Allows iteration over all types, including interfaces, in all loaded assemblies in the AsmMgr who's names match the string.
|
||||
/// Note: Will return the by-reference equivalent type if the type name is prefixed with "out " or "ref ".
|
||||
/// </summary>
|
||||
/// <param name="typeName">The string name of the type to search for.</param>
|
||||
/// <returns>An Enumerator for matching types. List will be empty if bad params are supplied.</returns>
|
||||
public IEnumerable<Type> GetTypesByName(string typeName);
|
||||
|
||||
/// <summary>
|
||||
/// Allows iteration over all types (including interfaces) in all loaded assemblies managed by the AsmMgr.
|
||||
/// Warning: High usage may result in performance issues.
|
||||
/// </summary>
|
||||
/// <returns>An Enumerator for iteration.</returns>
|
||||
public IEnumerable<Type> GetAllTypesInLoadedAssemblies();
|
||||
|
||||
/// <summary>
|
||||
/// Returns a list of all loaded ACLs.
|
||||
/// WARNING: References to these ACLs outside the AssemblyManager should be kept in a WeakReference in order
|
||||
/// to avoid causing issues with unloading/disposal.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public IEnumerable<AssemblyManager.LoadedACL> GetAllLoadedACLs();
|
||||
|
||||
#endregion
|
||||
|
||||
#region InternalAPI
|
||||
/*** Notes: Internal API uses the 'public' modifier because of the common and recommended use of publicized APIs
|
||||
* by third-party add-ins.
|
||||
*/
|
||||
|
||||
/// <summary>
|
||||
/// [Unsafe] Warning: only for use in nested threading functions. Requires care to manage access.
|
||||
/// Does not make any guarantees about the state of the ACL after the list has been returned.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public ImmutableList<AssemblyManager.LoadedACL> UnsafeGetAllLoadedACLs();
|
||||
|
||||
/// <summary>
|
||||
/// Used by content package and plugin management to stop unloading of a given ACL until all plugins have gracefully closed.
|
||||
/// </summary>
|
||||
public event System.Func<AssemblyManager.LoadedACL, bool> IsReadyToUnloadACL;
|
||||
|
||||
/// <summary>
|
||||
/// Compiles an assembly from supplied references and syntax trees into the specified AssemblyContextLoader.
|
||||
/// A new ACL will be created if the Guid supplied is Guid.Empty.
|
||||
/// </summary>
|
||||
/// <param name="compiledAssemblyName"></param>
|
||||
/// <param name="syntaxTree"></param>
|
||||
/// <param name="externalMetadataReferences"></param>
|
||||
/// <param name="compilationOptions"></param>
|
||||
/// <param name="friendlyName">A non-unique name for later reference. Optional, set to null if unused.</param>
|
||||
/// <param name="id">The guid of the assembly </param>
|
||||
/// <param name="externFileAssemblyRefs"></param>
|
||||
/// <returns></returns>
|
||||
public AssemblyLoadingSuccessState LoadAssemblyFromMemory([NotNull] string compiledAssemblyName,
|
||||
[NotNull] IEnumerable<SyntaxTree> syntaxTree,
|
||||
IEnumerable<MetadataReference> externalMetadataReferences,
|
||||
[NotNull] CSharpCompilationOptions compilationOptions,
|
||||
string friendlyName,
|
||||
ref Guid id,
|
||||
IEnumerable<Assembly> externFileAssemblyRefs = null);
|
||||
|
||||
/// <summary>
|
||||
/// Switches the ACL with the given Guid to Template Mode, which disables assembly name resolution for any assemblies loaded in it.
|
||||
/// These ACLs are intended to be used to host Assemblies for information only and not for code execution.
|
||||
/// WARNING: This process is irreversible.
|
||||
/// </summary>
|
||||
/// <param name="guid">Guid of the ACL.</param>
|
||||
/// <returns>Whether an ACL was found with the given ID.</returns>
|
||||
public bool SetACLToTemplateMode(Guid guid);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Tries to load all assemblies at the supplied file paths list into the ACl with the given Guid.
|
||||
/// If the supplied Guid is Empty, then a new ACl will be created and the Guid will be assigned to it.
|
||||
/// </summary>
|
||||
/// <param name="filePaths">List of assemblies to try and load.</param>
|
||||
/// <param name="friendlyName">A non-unique name for later reference. Optional.</param>
|
||||
/// <param name="id">Guid of the ACL or Empty if none specified. Guid of ACL will be assigned to this var.</param>
|
||||
/// <returns>Operation success messages.</returns>
|
||||
/// <exception cref="ArgumentNullException"></exception>
|
||||
public AssemblyLoadingSuccessState LoadAssembliesFromLocations([NotNull] IEnumerable<string> filePaths,
|
||||
string friendlyName, ref Guid id);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Tries to begin the disposal process of ACLs.
|
||||
/// </summary>
|
||||
/// <returns>Returns whether the unloading process could be initiated.</returns>
|
||||
public bool TryBeginDispose();
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether unloading is completed and updates the styate of the unloading cache.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool FinalizeDispose();
|
||||
|
||||
/// <summary>
|
||||
/// Tries to retrieve the LoadedACL with the given ID or null if none is found.
|
||||
/// WARNING: External references to this ACL with long lifespans should be kept in a WeakReference
|
||||
/// to avoid causing unloading/disposal issues.
|
||||
/// </summary>
|
||||
/// <param name="id">GUID of the ACL.</param>
|
||||
/// <param name="acl">The found ACL or null if none was found.</param>
|
||||
/// <returns>Whether an ACL was found.</returns>
|
||||
public bool TryGetACL(Guid id, out AssemblyManager.LoadedACL acl);
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Barotrauma.LuaCs.Configuration;
|
||||
using Barotrauma.LuaCs.Data;
|
||||
using Barotrauma.Networking;
|
||||
|
||||
namespace Barotrauma.LuaCs.Services;
|
||||
|
||||
public interface IConfigService : IService
|
||||
{
|
||||
/*
|
||||
* Resource Files.
|
||||
*/
|
||||
bool TryAddConfigs(ImmutableArray<IConfigResourceInfo> configResources);
|
||||
bool TryAddConfigsProfiles(ImmutableArray<IConfigProfileResourceInfo> configProfileResources);
|
||||
void RemoveConfigs(ImmutableArray<IConfigResourceInfo> configResources);
|
||||
void RemoveConfigsProfiles(ImmutableArray<IConfigProfileResourceInfo> configProfilesResources);
|
||||
|
||||
|
||||
/*
|
||||
* Already processed
|
||||
*/
|
||||
bool TryAddConfigs(ImmutableArray<IConfigInfo> configs);
|
||||
bool TryAddConfigsProfiles(ImmutableArray<IConfigProfileInfo> configProfiles);
|
||||
void RemoveConfigs(ImmutableArray<IConfigInfo> configs);
|
||||
void RemoveConfigsProfiles(ImmutableArray<IConfigProfileInfo> configProfiles);
|
||||
|
||||
/*
|
||||
* Immediate mode, does not have displayable functionality
|
||||
*/
|
||||
IConfigEntry<T> AddConfigEntry<T>(ContentPackage package, string name,
|
||||
T defaultValue,
|
||||
NetSync syncMode = NetSync.None,
|
||||
ClientPermissions permissions = ClientPermissions.None,
|
||||
Func<T, bool> valueChangePredicate = null,
|
||||
Action<IConfigEntry<T>> onValueChanged = null) where T : IConvertible, IEquatable<T>;
|
||||
|
||||
IConfigList AddConfigList(ContentPackage package, string name,
|
||||
int defaultIndex, IReadOnlyList<string> values,
|
||||
NetSync syncMode = NetSync.None,
|
||||
ClientPermissions permissions = ClientPermissions.None,
|
||||
Func<IConfigList, int, bool> valueChangePredicate = null,
|
||||
Action<IConfigList, int> onValueChanged = null);
|
||||
|
||||
IReadOnlyDictionary<string, IConfigBase> GetConfigsForPackage(ContentPackage package);
|
||||
IReadOnlyDictionary<string, IConfigBase> GetConfigsForPackage(string packageName);
|
||||
IReadOnlyDictionary<(ContentPackage, string), IConfigBase> GetAllConfigs();
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
namespace Barotrauma.LuaCs.Services;
|
||||
|
||||
public interface IHookManagementService : IService
|
||||
{
|
||||
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
using Barotrauma.LuaCs.Data;
|
||||
|
||||
namespace Barotrauma.LuaCs.Services;
|
||||
|
||||
public interface ILegacyConfigService : IService
|
||||
{
|
||||
bool TryBuildModConfigFromLegacy(ContentPackage package, out IModConfigInfo configInfo);
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using Barotrauma.LuaCs.Data;
|
||||
|
||||
namespace Barotrauma.LuaCs.Services;
|
||||
|
||||
public interface ILocalizationService : IService
|
||||
{
|
||||
IReadOnlyCollection<CultureInfo> GetLoadedLocales();
|
||||
void Remove(ImmutableArray<ILocalizationResourceInfo> localizations);
|
||||
bool TrySetCurrentCulture(CultureInfo culture);
|
||||
bool TrySetCurrentCulture(string cultureName);
|
||||
bool TryLoadLocalizations(ImmutableArray<ILocalizationResourceInfo> localizationResources);
|
||||
string GetLocalizedString(string key, string fallback);
|
||||
string GetLocalizedString(string key, CultureInfo targetCulture);
|
||||
bool TryRegisterLocalizationResolver(CultureInfo targetCulture, Func<string, CultureInfo, string> factoryResolver);
|
||||
bool ReplaceSymbols(string text, string symbolExpr);
|
||||
bool IsCurrentCultureSupported(IResourceCultureInfo culturesInfo);
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
using System;
|
||||
using Barotrauma.LuaCs.Data;
|
||||
using Barotrauma.LuaCs.Networking;
|
||||
using Barotrauma.Networking;
|
||||
|
||||
namespace Barotrauma.LuaCs.Services;
|
||||
|
||||
public interface INetworkingService : IService
|
||||
{
|
||||
bool IsActive { get; }
|
||||
bool IsSynchronized { get; }
|
||||
bool TryRegisterVar(INetVar var, NetSync mode, ClientPermissions permissions);
|
||||
void UnregisterVar(Guid varId);
|
||||
bool SendEvent(Guid varId);
|
||||
void SendMessageGlobal(string id, string message);
|
||||
void Synchronize();
|
||||
|
||||
#region LegacyAPI
|
||||
|
||||
bool RestrictMessageSize { get; set; }
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Globalization;
|
||||
using Barotrauma.LuaCs.Data;
|
||||
|
||||
namespace Barotrauma.LuaCs.Services;
|
||||
|
||||
public interface IPackageManagementService : IService
|
||||
{
|
||||
void AddPackages(ref ReadOnlySpan<(ContentPackage, bool)> packages,
|
||||
bool executeImmediately = false,
|
||||
bool errorOnFailures = false,
|
||||
bool errorOnExistingPackageFound = false);
|
||||
void LoadPackages(bool onlyUnloadedPackages = true, bool rescanPackages = false);
|
||||
void UnloadPackages(bool errorOnFailures = true);
|
||||
bool IsPackageLoaded(ContentPackage package);
|
||||
bool CheckDependencyLoaded(IPackageDependencyInfo info);
|
||||
bool CheckDependenciesLoaded(IEnumerable<IPackageDependencyInfo> infos, out IReadOnlyList<IPackageDependencyInfo> missingPackages);
|
||||
bool CheckEnvironmentSupported(IPlatformInfo platform);
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
namespace Barotrauma.LuaCs.Services;
|
||||
|
||||
public interface IPluginManagementService : IService
|
||||
{
|
||||
bool IsAssemblyLoadedGlobal(string friendlyName);
|
||||
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Barotrauma.LuaCs.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Base interface inherited by all services
|
||||
/// </summary>
|
||||
public interface IService : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns the service to its original state (post-instantiation).
|
||||
/// Allows a service instance to be reused without disposing of the instance.
|
||||
/// </summary>
|
||||
void Reset();
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.LuaCs.Services;
|
||||
|
||||
public interface IStorageService : IService
|
||||
{
|
||||
#region LocalGameData
|
||||
|
||||
bool TryLoadLocalXml(ContentPackage package, string localFilePath, out XDocument document);
|
||||
bool TryLoadLocalBinary(ContentPackage package, string localFilePath, out byte[] bytes);
|
||||
bool TryLoadLocalText(ContentPackage package, string localFilePath, out string text);
|
||||
bool FileExistsInLocalData(ContentPackage package, string localFilePath);
|
||||
|
||||
#endregion
|
||||
|
||||
#region ContentPackageData
|
||||
bool TryLoadPackageXml(ContentPackage package, string localFilePath, out XDocument document);
|
||||
bool TryLoadPackageBinary(ContentPackage package, string localFilePath, out byte[] bytes);
|
||||
bool TryLoadPackageText(ContentPackage package, string localFilePath, out string text);
|
||||
|
||||
ImmutableArray<bool> TryLoadPackageXmlFiles(ContentPackage package, ImmutableArray<string> localFilePath, out ImmutableArray<XDocument> document);
|
||||
ImmutableArray<bool> TryLoadPackageBinaryFiles(ContentPackage package, ImmutableArray<string> localFilePath, out ImmutableArray<byte[]> bytes);
|
||||
ImmutableArray<bool> TryLoadPackageTextFiles(ContentPackage package, ImmutableArray<string> localFilePath, out ImmutableArray<string> text);
|
||||
|
||||
bool FindFilesInPackage(ContentPackage package, string localSubfolder, string regexFilter, bool searchRecursively, out ImmutableArray<string> localFilePaths);
|
||||
bool FileExistsInPackage(ContentPackage package, string localFilePath);
|
||||
|
||||
#endregion
|
||||
|
||||
#region AbsolutePaths
|
||||
|
||||
bool TryLoadXml(string filePath, out XDocument document);
|
||||
bool TrySaveXml(string filePath, in XDocument document);
|
||||
bool TryLoadBinary(string filePath, out byte[] bytes);
|
||||
bool TrySaveBinary(string filePath, in byte[] bytes);
|
||||
bool TryLoadText(string filePath, out string text);
|
||||
bool TrySaveText(string filePath, string text);
|
||||
bool FileExists(string filePath);
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -129,6 +129,11 @@ public partial class LoggerService : ILoggerService
|
||||
#endif
|
||||
}
|
||||
|
||||
public void LogResults(FluentResults.Result result)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void LogDebug(string message, Color? color = null)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
@@ -145,5 +150,5 @@ public partial class LoggerService : ILoggerService
|
||||
}
|
||||
|
||||
public void Dispose() { }
|
||||
public void Reset() { }
|
||||
public FluentResults.Result Reset() => FluentResults.Result.Ok();
|
||||
}
|
||||
|
||||
@@ -1,6 +1,176 @@
|
||||
namespace Barotrauma.LuaCs.Services;
|
||||
using Barotrauma.LuaCs.Data;
|
||||
using MoonSharp.Interpreter;
|
||||
using MoonSharp.Interpreter.Interop;
|
||||
using System;
|
||||
using System.Collections.Immutable;
|
||||
using System.Reflection;
|
||||
|
||||
public class LuaScriptService
|
||||
namespace Barotrauma.LuaCs.Services;
|
||||
|
||||
public class LuaScriptService : ILuaScriptService, ILuaScriptManagementService
|
||||
{
|
||||
|
||||
public void AddField(IUserDataDescriptor descriptor, string fieldName, DynValue value)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void AddMethod(IUserDataDescriptor descriptor, string methodName, object function)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public FluentResults.Result AddScriptFiles(ImmutableArray<ILuaResourceInfo> luaResource)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public object CreateEnumTable(string typeName)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public object CreateStatic(string typeName)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public DynValue CreateUserDataFromDescriptor(DynValue scriptObject, IUserDataDescriptor descriptor)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public DynValue CreateUserDataFromType(DynValue scriptObject, Type desiredType)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public FluentResults.Result ExecuteLoadedScripts(ContentPackage package, bool pauseExecutionOnError = false, bool verboseLogging = false)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public FluentResults.Result ExecuteLoadedScripts(ImmutableArray<ILuaResourceInfo> scripts, bool pauseExecutionOnError = false, bool verboseLogging = false)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public FluentResults.Result ExecuteLoadedScripts(bool pauseExecutionOnError = false, bool verboseLogging = false)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public FluentResults.Result ExecuteScripts(bool pauseExecutionOnScriptError = false, bool verboseLogging = false)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public FieldInfo FindFieldRecursively(Type type, string fieldName)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public MethodInfo FindMethodRecursively(Type type, string methodName, Type[] types = null)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public PropertyInfo FindPropertyRecursively(Type type, string propertyName)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public ImmutableArray<ILuaResourceInfo> GetScriptResources()
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public bool HasMember(object obj, string memberName)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public bool IsRegistered(Type type)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public bool IsTargetType(object obj, string typeName)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void MakeFieldAccessible(IUserDataDescriptor descriptor, string fieldName)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void MakeMethodAccessible(IUserDataDescriptor descriptor, string methodName, string[] parameters = null)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void MakePropertyAccessible(IUserDataDescriptor descriptor, string propertyName)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public IUserDataDescriptor RegisterGenericType(Type type)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public IUserDataDescriptor RegisterGenericType(string typeName, params string[] typeNameArgs)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public IUserDataDescriptor RegisterType(Type type)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public IUserDataDescriptor RegisterType(string typeName)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void RemoveMember(IUserDataDescriptor descriptor, string memberName)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void RemoveScriptFiles(ImmutableArray<ILuaResourceInfo> luaResource)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public FluentResults.Result Reset()
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public string TypeOf(object obj)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void UnregisterAllTypes()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void UnregisterType(Type type)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void UnregisterType(string typeName)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
using Barotrauma.LuaCs.Services;
|
||||
using Barotrauma.Networking;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Barotrauma.LuaCs.Networking;
|
||||
|
||||
internal partial class NetworkingService : INetworkingService
|
||||
{
|
||||
private enum LuaCsClientToServer
|
||||
{
|
||||
NetMessageId,
|
||||
NetMessageString,
|
||||
RequestSingleId,
|
||||
RequestAllIds,
|
||||
}
|
||||
|
||||
private enum LuaCsServerToClient
|
||||
{
|
||||
NetMessageId,
|
||||
NetMessageString,
|
||||
ReceiveIds
|
||||
}
|
||||
|
||||
private Dictionary<Guid, INetVar> netVars = new Dictionary<Guid, INetVar>();
|
||||
private Dictionary<Guid, NetMessageReceived> netReceives = new Dictionary<Guid, NetMessageReceived>();
|
||||
private Dictionary<ushort, Guid> packetToId = new Dictionary<ushort, Guid>();
|
||||
private Dictionary<Guid, ushort> idToPacket = new Dictionary<Guid, ushort>();
|
||||
|
||||
public bool IsActive
|
||||
{
|
||||
get
|
||||
{
|
||||
return GameMain.NetworkMember != null; // ehh?
|
||||
}
|
||||
}
|
||||
public bool IsSynchronized { get; private set; }
|
||||
public bool IsDisposed { get; private set; }
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
#if SERVER
|
||||
IsSynchronized = true;
|
||||
#elif CLIENT
|
||||
SendSyncMessage();
|
||||
#endif
|
||||
}
|
||||
|
||||
public void RegisterNetVar(INetVar netVar)
|
||||
{
|
||||
netVars[netVar.InstanceId] = netVar;
|
||||
|
||||
netReceives[netVar.InstanceId] = (IReadMessage netMessage) =>
|
||||
{
|
||||
INetReadMessage internalMind = new NetReadMessage();
|
||||
internalMind.SetMessage(netMessage);
|
||||
netVar.ReadNetMessage(internalMind);
|
||||
};
|
||||
}
|
||||
|
||||
public void SendNetVar(INetVar netVar)
|
||||
{
|
||||
if (netVars.ContainsKey(netVar.InstanceId))
|
||||
{
|
||||
INetWriteMessage message = Start(netVar.InstanceId);
|
||||
netVar.WriteNetMessage(message);
|
||||
Send(message.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(Guid netId, NetMessageReceived callback)
|
||||
{
|
||||
#if SERVER
|
||||
RegisterId(netId);
|
||||
#elif CLIENT
|
||||
RequestId(netId);
|
||||
#endif
|
||||
netReceives[netId] = callback;
|
||||
}
|
||||
|
||||
private void HandleNetMessage(IReadMessage netMessage, Guid netId, Client client = null)
|
||||
{
|
||||
if (netReceives.ContainsKey(netId))
|
||||
{
|
||||
try
|
||||
{
|
||||
netReceives[netId](netMessage);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LuaCsLogger.LogError($"Exception thrown inside NetMessageReceive({netId})", LuaCsMessageOrigin.CSharpMod);
|
||||
LuaCsLogger.HandleException(e, LuaCsMessageOrigin.CSharpMod);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (GameSettings.CurrentConfig.VerboseLogging)
|
||||
{
|
||||
#if SERVER
|
||||
LuaCsLogger.LogError($"Received NetMessage for unknown netid {netId} from {GameServer.ClientLogName(client)}.");
|
||||
#else
|
||||
LuaCsLogger.LogError($"Received NetMessage for unknown netid {netId} from server.");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleNetMessageString(IReadMessage netMessage, Client client = null)
|
||||
{
|
||||
Guid guid = new Guid(netMessage.ReadBytes(16));
|
||||
|
||||
HandleNetMessage(netMessage, guid, client);
|
||||
}
|
||||
|
||||
public FluentResults.Result Reset()
|
||||
{
|
||||
IsSynchronized = false;
|
||||
netReceives = new Dictionary<Guid, NetMessageReceived>();
|
||||
packetToId = new Dictionary<ushort, Guid>();
|
||||
idToPacket = new Dictionary<Guid, ushort>();
|
||||
return FluentResults.Result.Ok();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
IsDisposed = true;
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,41 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.LuaCs.Data;
|
||||
using Barotrauma.Steam;
|
||||
using FluentResults;
|
||||
using FluentResults.LuaCs;
|
||||
using QuikGraph;
|
||||
|
||||
namespace Barotrauma.LuaCs.Services;
|
||||
|
||||
public class PackageManagementService : IPackageManagementService, IPluginManagementService
|
||||
public class PackageManagementService : IPackageManagementService
|
||||
{
|
||||
private readonly Func<IPackageService> _contentPackageServiceFactory;
|
||||
private readonly Lazy<IAssemblyManagementService> _assemblyManagementService;
|
||||
private readonly ConcurrentDictionary<ContentPackage, IPackageService> _contentPackages = new();
|
||||
private readonly ConcurrentQueue<LoadablePackage> _queuedPackages = new();
|
||||
private readonly ConcurrentDictionary<DependencyEntryKey, IPackageDependencyInfo> _packageDependencyInfos = new();
|
||||
|
||||
/// <summary>
|
||||
/// ConcurrentDictionary handles access/read synchronization. This is to ensure that we are not trying to
|
||||
/// access the collection during a load/unload/modify operation.
|
||||
/// </summary>
|
||||
private readonly ReaderWriterLockSlim _contentPackagesModificationsLock = new();
|
||||
/// <summary>
|
||||
/// This lock ensures that we are not adding new entries to the queue between when we read the contents and
|
||||
/// empty the buffer.
|
||||
/// </summary>
|
||||
private readonly ReaderWriterLockSlim _packageQueueProcessingLock = new();
|
||||
|
||||
public PackageManagementService(
|
||||
Func<IPackageService> getPackageService,
|
||||
Lazy<IAssemblyManagementService> assemblyManagementService)
|
||||
@@ -17,55 +44,418 @@ public class PackageManagementService : IPackageManagementService, IPluginManage
|
||||
this._assemblyManagementService = assemblyManagementService;
|
||||
}
|
||||
|
||||
#region STATE_RESET
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// TODO release managed resources here
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
public FluentResults.Result Reset()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public bool IsAssemblyLoadedGlobal(string friendlyName)
|
||||
#endregion
|
||||
|
||||
public void QueuePackages(ImmutableArray<LoadablePackage> packages)
|
||||
{
|
||||
_packageQueueProcessingLock.EnterReadLock();
|
||||
try
|
||||
{
|
||||
foreach (LoadablePackage package in packages)
|
||||
_queuedPackages.Enqueue(package);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_packageQueueProcessingLock.ExitReadLock();
|
||||
}
|
||||
}
|
||||
|
||||
public FluentResults.Result ParseQueuedPackages(bool loadParallel = true, bool reportFailOnDuplicates = false)
|
||||
{
|
||||
if (!ModUtils.Environment.IsMainThread)
|
||||
throw new InvalidOperationException($"{nameof(ParseQueuedPackages)}: This method can only be called on the main thread.");
|
||||
|
||||
ImmutableArray<LoadablePackage> packagesToProcess = ImmutableArray<LoadablePackage>.Empty;
|
||||
|
||||
_packageQueueProcessingLock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
Interlocked.MemoryBarrier();
|
||||
if (_queuedPackages.IsEmpty)
|
||||
return FluentResults.Result.Ok().WithSuccess($"{nameof(ParseQueuedPackages)}: The Queue is empty.");
|
||||
packagesToProcess = _queuedPackages.Where(p => p.Package is not null)
|
||||
.Distinct().ToImmutableArray();
|
||||
_queuedPackages.Clear();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_packageQueueProcessingLock.ExitWriteLock();
|
||||
}
|
||||
|
||||
FluentResults.Result[] loadResults = new FluentResults.Result[packagesToProcess.Length];
|
||||
FluentResults.Result res = new FluentResults.Result();
|
||||
|
||||
// Load ModConfigInfo
|
||||
_contentPackagesModificationsLock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
Stopwatch stopwatch = new Stopwatch();
|
||||
stopwatch.Start();
|
||||
|
||||
Interlocked.MemoryBarrier();
|
||||
if (loadParallel)
|
||||
{
|
||||
Parallel.For(0, loadResults.Length, new ParallelOptions()
|
||||
{
|
||||
/*
|
||||
* This is an IO-bound operation. The purpose of parallelism here is to allow loaded package
|
||||
* data to be processed while another package is waiting on the storage device for its info.
|
||||
*/
|
||||
MaxDegreeOfParallelism = 2
|
||||
},i =>
|
||||
{
|
||||
loadResults[i] = LoadPackageInfo(packagesToProcess[i]);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < loadResults.Length; i++)
|
||||
{
|
||||
loadResults[i] = LoadPackageInfo(packagesToProcess[i]);
|
||||
}
|
||||
}
|
||||
|
||||
stopwatch.Stop();
|
||||
|
||||
res.WithSuccess(new Success(
|
||||
$"Completed parsing of {loadResults.Length} packages in {stopwatch.ElapsedMilliseconds} milliseconds."));
|
||||
|
||||
for (int i = 0; i < loadResults.Length; i++)
|
||||
{
|
||||
res = loadResults[i].IsSuccess
|
||||
? res.WithSuccesses(loadResults[i].Successes)
|
||||
: res.WithErrors(loadResults[i].Errors);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
catch (AggregateException ae)
|
||||
{
|
||||
return FluentResults.Result.Fail(new Error($"{nameof(ParseQueuedPackages)}: Failed to load packages! AE.")
|
||||
.WithMetadata(MetadataType.ExceptionDetails, ae.InnerException?.Message ?? ae.Message)
|
||||
.WithMetadata(MetadataType.StackTrace, ae.StackTrace)
|
||||
.WithMetadata(MetadataType.ExceptionObject, this));
|
||||
}
|
||||
catch (ArgumentNullException ane)
|
||||
{
|
||||
return FluentResults.Result.Fail(
|
||||
new Error($"{nameof(ParseQueuedPackages)}: Failed to load packages! ANE.")
|
||||
.WithMetadata(MetadataType.ExceptionDetails, ane.InnerException?.Message ?? ane.Message)
|
||||
.WithMetadata(MetadataType.StackTrace, ane.StackTrace)
|
||||
.WithMetadata(MetadataType.ExceptionObject, this));
|
||||
}
|
||||
finally
|
||||
{
|
||||
_contentPackagesModificationsLock.ExitWriteLock();
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Helper functions
|
||||
*/
|
||||
|
||||
// register in the list so we can check against it.
|
||||
FluentResults.Result LoadPackageInfo(LoadablePackage package)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (package.Package == null)
|
||||
{
|
||||
return FluentResults.Result.Fail(
|
||||
new Error($"{nameof(LoadPackageInfo)}: Package is null!")
|
||||
.WithMetadata(MetadataType.ExceptionObject, this)
|
||||
.WithMetadata(MetadataType.RootObject, package));
|
||||
}
|
||||
|
||||
if (_contentPackages.TryGetValue(package.Package, out var packageService))
|
||||
{
|
||||
if (reportFailOnDuplicates)
|
||||
{
|
||||
return FluentResults.Result.Fail(new Error($"The package {package.Package?.Name} is already loaded.")
|
||||
.WithMetadata(MetadataType.ExceptionObject, this)
|
||||
.WithMetadata(MetadataType.RootObject, package.Package));
|
||||
}
|
||||
|
||||
return FluentResults.Result.Ok();
|
||||
}
|
||||
|
||||
packageService = _contentPackageServiceFactory.Invoke();
|
||||
_contentPackages[package.Package] = packageService;
|
||||
return packageService.LoadResourcesInfo(package);
|
||||
}
|
||||
catch (NullReferenceException nre)
|
||||
{
|
||||
return FluentResults.Result.Fail(new Error($"{nameof(LoadPackageInfo)}: NRE while loading package {package.Package?.Name}!")
|
||||
.WithMetadata(MetadataType.ExceptionObject, this)
|
||||
.WithMetadata(MetadataType.StackTrace, nre.StackTrace ?? "StackTrace not available")
|
||||
.WithMetadata(MetadataType.ExceptionDetails, nre.InnerException?.Message ?? nre.Message)
|
||||
.WithMetadata(MetadataType.RootObject, package));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public FluentResults.Result LoadPackageConfigsResourcesGroup(bool loadParallel = true)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void AddPackages(ref ReadOnlySpan<(ContentPackage, bool)> packages, bool executeImmediately = false, bool errorOnFailures = false,
|
||||
bool errorOnExistingPackageFound = false)
|
||||
public FluentResults.Result LoadAllPackageResources(bool loadParallel = true, bool safeResourcesOnly = true)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void LoadPackages(bool onlyUnloadedPackages = true, bool rescanPackages = false)
|
||||
public FluentResults.Result UnloadPackages()
|
||||
{
|
||||
if (!ModUtils.Environment.IsMainThread)
|
||||
{
|
||||
return FluentResults.Result.Fail(
|
||||
new ExceptionalError(new InvalidOperationException($"{nameof(UnloadPackages)}: This method can only be called on the main thread."))
|
||||
.WithMetadata(MetadataType.ExceptionObject, this));
|
||||
}
|
||||
|
||||
var res = new FluentResults.Result();
|
||||
_contentPackagesModificationsLock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
// TODO: Finish him
|
||||
}
|
||||
finally
|
||||
{
|
||||
_contentPackagesModificationsLock.ExitWriteLock();
|
||||
}
|
||||
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void UnloadPackages(bool errorOnFailures = true)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
public bool IsPackageLoaded(ContentPackage package) => package is not null && _contentPackages.ContainsKey(package);
|
||||
|
||||
public bool IsPackageLoaded(ContentPackage package)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
public bool CheckDependencyLoaded(IPackageDependencyInfo info) =>
|
||||
info is not null && IsPackageLoaded(info.DependencyPackage);
|
||||
|
||||
public bool CheckDependencyLoaded(IPackageDependencyInfo info)
|
||||
public bool CheckDependenciesLoaded([NotNull]IEnumerable<IPackageDependencyInfo> infos, out ImmutableArray<IPackageDependencyInfo> missingPackages)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var missing = ImmutableArray.CreateBuilder<IPackageDependencyInfo>();
|
||||
missing.AddRange(infos
|
||||
.Where(i => i.DependencyPackage is not null)
|
||||
.DistinctBy(i => i.DependencyPackage)
|
||||
.Where(i => !CheckDependencyLoaded(i)));
|
||||
missingPackages = missing.MoveToImmutable();
|
||||
return missingPackages.Length == 0;
|
||||
}
|
||||
|
||||
public bool CheckDependenciesLoaded(IEnumerable<IPackageDependencyInfo> infos, out IReadOnlyList<IPackageDependencyInfo> missingPackages)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
|
||||
public bool CheckEnvironmentSupported(IPlatformInfo platform)
|
||||
{
|
||||
return (platform.SupportedPlatforms & ModUtils.Environment.CurrentPlatform) > 0
|
||||
&& (platform.SupportedTargets & ModUtils.Environment.CurrentTarget) > 0;
|
||||
}
|
||||
|
||||
public Result<IPackageDependencyInfo> GetPackageDependencyInfoRecord(ContentPackage package, bool addIfMissing = false)
|
||||
{
|
||||
if (package is null)
|
||||
{
|
||||
return new FluentResults.Result<IPackageDependencyInfo>()
|
||||
.WithError(new Error($"{nameof(GetPackageDependencyInfoRecord)}: Package is null!")
|
||||
.WithMetadata(MetadataType.ExceptionObject, this));
|
||||
}
|
||||
|
||||
if (_packageDependencyInfos.TryGetValue(package, out var result))
|
||||
{
|
||||
return new FluentResults.Result<IPackageDependencyInfo>()
|
||||
.WithValue(result);
|
||||
}
|
||||
|
||||
if (addIfMissing)
|
||||
{
|
||||
return AddDependencyRecord(package, package.Name, package.Path,
|
||||
package.TryExtractSteamWorkshopId(out var id) ? id.Value : 0,
|
||||
false);
|
||||
}
|
||||
|
||||
return FluentResults.Result.Fail<IPackageDependencyInfo>(new Error($"Could not find package {package.Name}!")
|
||||
.WithMetadata(MetadataType.ExceptionObject, this)
|
||||
.WithMetadata(MetadataType.RootObject, package));
|
||||
}
|
||||
|
||||
public Result<IPackageDependencyInfo> GetPackageDependencyInfoRecord(ulong steamWorkshopId, string packageName, string folderPath = null,
|
||||
bool addIfMissing = false)
|
||||
{
|
||||
if (packageName.IsNullOrWhiteSpace() || folderPath.IsNullOrWhiteSpace())
|
||||
{
|
||||
return new FluentResults.Result<IPackageDependencyInfo>()
|
||||
.WithError(new Error($"{nameof(GetPackageDependencyInfoRecord)}: folder path and/or package name are null!")
|
||||
.WithMetadata(MetadataType.ExceptionObject, this));
|
||||
}
|
||||
|
||||
if (_packageDependencyInfos.TryGetValue((packageName,steamWorkshopId,folderPath), out var result))
|
||||
{
|
||||
return new FluentResults.Result<IPackageDependencyInfo>()
|
||||
.WithValue(result);
|
||||
}
|
||||
|
||||
// TODO: Finish this
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Result<IPackageDependencyInfo> GetPackageDependencyInfoRecord(string folderPath)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
|
||||
public IPackageDependencyInfo CreateOrphanPackageDependencyInfoRecord(
|
||||
string packageName,
|
||||
string packagePath,
|
||||
ulong steamWorkshopId)
|
||||
{
|
||||
return new DependencyInfo()
|
||||
{
|
||||
DependencyPackage = null,
|
||||
FallbackPackageName = packageName,
|
||||
FolderPath = packagePath.IsNullOrWhiteSpace() ? null : System.IO.Path.GetFullPath(packagePath),
|
||||
SteamWorkshopId = steamWorkshopId,
|
||||
IsMissing = true,
|
||||
IsWorkshopInstallation = false
|
||||
};
|
||||
}
|
||||
|
||||
private Result<IPackageDependencyInfo> AddDependencyRecord(
|
||||
ContentPackage package,
|
||||
string packageName,
|
||||
string folderPath,
|
||||
ulong steamWorkshopId,
|
||||
bool isMissing)
|
||||
{
|
||||
// TODO: Redo
|
||||
try
|
||||
{
|
||||
var dependencyInfo = new DependencyInfo()
|
||||
{
|
||||
DependencyPackage = package,
|
||||
FallbackPackageName = packageName,
|
||||
FolderPath = System.IO.Path.GetFullPath(folderPath),
|
||||
SteamWorkshopId = steamWorkshopId,
|
||||
IsMissing = isMissing,
|
||||
IsWorkshopInstallation = steamWorkshopId != 0
|
||||
};
|
||||
if (package is not null)
|
||||
{
|
||||
_packageDependencyInfos.AddOrUpdate(package, pack => dependencyInfo,
|
||||
(pack, dep) => dependencyInfo);
|
||||
}
|
||||
return new FluentResults.Result<IPackageDependencyInfo>()
|
||||
.WithValue(dependencyInfo)
|
||||
.WithSuccess($"New value created.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new FluentResults.Result<IPackageDependencyInfo>()
|
||||
.WithError(new ExceptionalError(ex)
|
||||
.WithMetadata(MetadataType.ExceptionObject, this)
|
||||
.WithMetadata(MetadataType.ExceptionDetails, ex.Message)
|
||||
.WithMetadata(MetadataType.RootObject, package)
|
||||
.WithMetadata(MetadataType.StackTrace, ex.StackTrace ?? "StackTrace not available"));
|
||||
}
|
||||
}
|
||||
|
||||
private readonly record struct DependencyEntryKey : IEqualityComparer<DependencyEntryKey>, IEquatable<DependencyEntryKey>
|
||||
{
|
||||
public ContentPackage Package { get; init; }
|
||||
public string FolderPath { get; init; }
|
||||
public string PackageName { get; init; }
|
||||
public ulong SteamWorkshopId { get; init; }
|
||||
|
||||
public DependencyEntryKey(ContentPackage package)
|
||||
{
|
||||
Package = package ?? throw new ArgumentNullException(nameof(package), $"{nameof(DependencyEntryKey)}.ctor: Package cannot be null!");
|
||||
PackageName = package.Name;
|
||||
SteamWorkshopId = package.TryExtractSteamWorkshopId(out var id) ? id.Value : (ulong)0;
|
||||
FolderPath = package.Path;
|
||||
}
|
||||
|
||||
public DependencyEntryKey(string packageName, string folderPath, ulong steamWorkshopId)
|
||||
{
|
||||
PackageName = packageName;
|
||||
SteamWorkshopId = steamWorkshopId;
|
||||
FolderPath = folderPath;
|
||||
Package = null;
|
||||
}
|
||||
|
||||
public DependencyEntryKey(string packageName, ulong steamWorkshopId)
|
||||
{
|
||||
PackageName = packageName;
|
||||
SteamWorkshopId = steamWorkshopId;
|
||||
FolderPath = null;
|
||||
Package = null;
|
||||
}
|
||||
|
||||
public bool Equals(DependencyEntryKey other)
|
||||
{
|
||||
return Equals(this, other);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return GetHashCode(this);
|
||||
}
|
||||
|
||||
public bool Equals(DependencyEntryKey x, DependencyEntryKey y)
|
||||
{
|
||||
if (x == y)
|
||||
return true;
|
||||
|
||||
if (x.Package is not null && y.Package is not null && x.Package == Package)
|
||||
return true;
|
||||
|
||||
// folder should be a unique key if not unset.
|
||||
if (!x.FolderPath.IsNullOrWhiteSpace() && !y.FolderPath.IsNullOrWhiteSpace() &&
|
||||
x.FolderPath == FolderPath)
|
||||
return true;
|
||||
|
||||
if (!x.PackageName.IsNullOrWhiteSpace() && !y.PackageName.IsNullOrWhiteSpace()
|
||||
&& x.SteamWorkshopId != 0 && y.SteamWorkshopId != 0)
|
||||
return x.PackageName == y.PackageName && x.SteamWorkshopId == y.SteamWorkshopId;
|
||||
|
||||
if (!x.PackageName.IsNullOrWhiteSpace() && !y.PackageName.IsNullOrWhiteSpace() && x.PackageName == PackageName)
|
||||
return true;
|
||||
|
||||
if (x.SteamWorkshopId != 0 && y.SteamWorkshopId != 0 &&
|
||||
x.SteamWorkshopId == y.SteamWorkshopId)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public int GetHashCode(DependencyEntryKey obj)
|
||||
{
|
||||
if (!obj.PackageName.IsNullOrWhiteSpace())
|
||||
return obj.PackageName.GetHashCode();
|
||||
if (obj.SteamWorkshopId != 0)
|
||||
return obj.SteamWorkshopId.GetHashCode();
|
||||
if (obj.Package is not null)
|
||||
return obj.Package.GetHashCode();
|
||||
// We don't want to check the FolderPath because we want to resolve dependencies using packages
|
||||
// that might be local instead in the workshop folder.
|
||||
return 2342568; // random const value: collisions are fine as we want to call Equals()
|
||||
}
|
||||
|
||||
public static implicit operator DependencyEntryKey(ContentPackage package) => new(package);
|
||||
public static implicit operator DependencyEntryKey((string packageName, ulong steamWorkshopId) tuple1) =>
|
||||
new (tuple1.packageName, tuple1.steamWorkshopId);
|
||||
public static implicit operator DependencyEntryKey((string packageName, ulong steamWorkshopId, string folderPath) tuple1) =>
|
||||
new (tuple1.packageName, tuple1.folderPath, tuple1.steamWorkshopId);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -10,6 +10,9 @@ using System.Threading;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.LuaCs.Data;
|
||||
using Barotrauma.LuaCs.Services.Processing;
|
||||
using FluentResults;
|
||||
using FluentResults.LuaCs;
|
||||
using OneOf;
|
||||
|
||||
namespace Barotrauma.LuaCs.Services;
|
||||
|
||||
@@ -20,8 +23,7 @@ public partial class PackageService : IPackageService
|
||||
|
||||
|
||||
// mod config / package scanners/parsers
|
||||
private readonly Lazy<IXmlModConfigConverterService> _modConfigConverterService;
|
||||
private readonly Lazy<ILegacyConfigService> _legacyConfigService;
|
||||
private readonly Lazy<IModConfigParserService> _configParserService;
|
||||
private readonly Lazy<ILuaScriptService> _luaScriptService;
|
||||
private readonly Lazy<ILocalizationService> _localizationService;
|
||||
private readonly Lazy<IPluginService> _pluginService;
|
||||
@@ -35,31 +37,32 @@ public partial class PackageService : IPackageService
|
||||
// state monitors
|
||||
private int _configsLoaded, _localizationsLoaded, _luaScriptsLoaded, _pluginsLoaded, _isDisposed;
|
||||
private int _loadingOperationsRunning;
|
||||
private int _isEnabledInModList;
|
||||
|
||||
public bool ConfigsLoaded
|
||||
{
|
||||
get => GetThreadSafeBool(ref _configsLoaded);
|
||||
private set => SetThreadSafeBool(ref _configsLoaded, value);
|
||||
get => ModUtils.Threading.GetBool(ref _configsLoaded);
|
||||
private set => ModUtils.Threading.SetBool(ref _configsLoaded, value);
|
||||
}
|
||||
public bool LocalizationsLoaded
|
||||
{
|
||||
get => GetThreadSafeBool(ref _localizationsLoaded);
|
||||
private set => SetThreadSafeBool(ref _localizationsLoaded, value);
|
||||
get => ModUtils.Threading.GetBool(ref _localizationsLoaded);
|
||||
private set => ModUtils.Threading.SetBool(ref _localizationsLoaded, value);
|
||||
}
|
||||
public bool LuaScriptsLoaded
|
||||
{
|
||||
get => GetThreadSafeBool(ref _luaScriptsLoaded);
|
||||
private set => SetThreadSafeBool(ref _luaScriptsLoaded, value);
|
||||
get => ModUtils.Threading.GetBool(ref _luaScriptsLoaded);
|
||||
private set => ModUtils.Threading.SetBool(ref _luaScriptsLoaded, value);
|
||||
}
|
||||
public bool PluginsLoaded
|
||||
{
|
||||
get => GetThreadSafeBool(ref _pluginsLoaded);
|
||||
private set => SetThreadSafeBool(ref _pluginsLoaded, value);
|
||||
get => ModUtils.Threading.GetBool(ref _pluginsLoaded);
|
||||
private set => ModUtils.Threading.SetBool(ref _pluginsLoaded, value);
|
||||
}
|
||||
public bool IsDisposed
|
||||
{
|
||||
get => GetThreadSafeBool(ref _isDisposed);
|
||||
private set => SetThreadSafeBool(ref _isDisposed, value);
|
||||
get => ModUtils.Threading.GetBool(ref _isDisposed);
|
||||
private set => ModUtils.Threading.SetBool(ref _isDisposed, value);
|
||||
}
|
||||
|
||||
private bool LoadingOperationsRunning
|
||||
@@ -146,6 +149,12 @@ public partial class PackageService : IPackageService
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsEnabledInModList
|
||||
{
|
||||
get => ModUtils.Threading.GetBool(ref _isEnabledInModList);
|
||||
private set => ModUtils.Threading.SetBool(ref _isEnabledInModList, value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public ImmutableArray<CultureInfo> SupportedCultures => ModConfigInfo?.SupportedCultures ?? ImmutableArray<CultureInfo>.Empty;
|
||||
@@ -159,43 +168,48 @@ public partial class PackageService : IPackageService
|
||||
|
||||
#region PublicAPI
|
||||
|
||||
public bool TryLoadResourcesInfo(ContentPackage package)
|
||||
public FluentResults.Result LoadResourcesInfo(LoadablePackage cpackage)
|
||||
{
|
||||
if (cpackage.Package == null)
|
||||
{
|
||||
return FluentResults.Result.Fail(new Error($"{nameof(LoadResourcesInfo)}: Package is null!")
|
||||
.WithMetadata(MetadataType.ExceptionObject,this)
|
||||
.WithMetadata(MetadataType.RootObject, cpackage));
|
||||
}
|
||||
ContentPackage package = cpackage.Package;
|
||||
|
||||
_operationsUsageLock.EnterWriteLock();
|
||||
LoadingOperationsRunning = true;
|
||||
try
|
||||
{
|
||||
if (IsDisposed)
|
||||
{
|
||||
throw new ObjectDisposedException($"This package service instance is disposed!");
|
||||
return FluentResults.Result.Fail(
|
||||
new Error("Service is disposed.")
|
||||
.WithMetadata(MetadataType.ExceptionObject, this)
|
||||
.WithMetadata(MetadataType.RootObject, package));
|
||||
}
|
||||
|
||||
// try loading the ModConfig.xml. If it fails, use the Legacy loader to try and construct one from the package structure.
|
||||
if (_storageService.TryLoadPackageXml(package, "ModConfig.xml", out var configXml)
|
||||
&& configXml.Root is not null)
|
||||
var res = _configParserService.Value.BuildConfigForPackage(package);
|
||||
|
||||
if (res.IsFailed)
|
||||
{
|
||||
if (_modConfigConverterService.Value.TryParseResource(configXml.Root, out IModConfigInfo configInfo))
|
||||
{
|
||||
ModConfigInfo = configInfo;
|
||||
}
|
||||
else
|
||||
{
|
||||
_loggerService.LogError(
|
||||
$"Failed to parse ModConfig.xml for package {package.Name}, package mod content not loaded.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (_legacyConfigService.Value.TryBuildModConfigFromLegacy(package, out var legacyConfig))
|
||||
{
|
||||
ModConfigInfo = legacyConfig;
|
||||
}
|
||||
else
|
||||
{
|
||||
// vanilla mod or broken
|
||||
return false;
|
||||
return FluentResults.Result.Fail(res.Errors)
|
||||
.WithError(new Error("PackageService failed to load ModConfigInfo")
|
||||
.WithMetadata(MetadataType.ExceptionObject, _configParserService)
|
||||
.WithMetadata(MetadataType.RootObject, package));
|
||||
}
|
||||
|
||||
return true;
|
||||
this.ModConfigInfo = res.Value;
|
||||
this.IsEnabledInModList = cpackage.IsEnabled;
|
||||
return FluentResults.Result.Ok();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return FluentResults.Result.Fail(new Error(e.Message)
|
||||
.WithMetadata(MetadataType.ExceptionObject, this)
|
||||
.WithMetadata(MetadataType.RootObject, package)
|
||||
.WithMetadata(MetadataType.StackTrace, e.StackTrace));
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -204,31 +218,19 @@ public partial class PackageService : IPackageService
|
||||
}
|
||||
}
|
||||
|
||||
public void LoadPlugins([NotNull]IAssembliesResourcesInfo assembliesInfo, bool ignoreDependencySorting = false)
|
||||
public FluentResults.Result LoadPlugins([NotNull]IAssembliesResourcesInfo assembliesInfo, bool ignoreDependencySorting = false)
|
||||
{
|
||||
_operationsUsageLock.EnterReadLock();
|
||||
LoadingOperationsRunning = true;
|
||||
try
|
||||
{
|
||||
if (IsDisposed)
|
||||
if (CheckResourceSanitation(OneOf<IAssembliesResourcesInfo, ILocalizationsResourcesInfo,
|
||||
IConfigsResourcesInfo, IConfigProfilesResourcesInfo, ILuaScriptsResourcesInfo>
|
||||
.FromT0(assembliesInfo)) is { IsFailed: true } failed)
|
||||
{
|
||||
throw new ObjectDisposedException($"This package service instance is disposed!");
|
||||
return failed;
|
||||
}
|
||||
|
||||
SanitationChecksCore(assembliesInfo, "assemblies", nameof(LoadPlugins));
|
||||
SanitationChecksEnumerable(assembliesInfo.Assemblies, "assemblies", nameof(LoadPlugins));
|
||||
|
||||
#if DEBUG
|
||||
assembliesInfo.Assemblies.ForEach(ari =>
|
||||
{
|
||||
if (!this.Assemblies.Contains(ari))
|
||||
{
|
||||
throw new ArgumentException(
|
||||
$"Package Service: tried to load the assembly resource {ari.InternalName} for package {this.Package.Name} but it is not in the list for this package.");
|
||||
}
|
||||
});
|
||||
#endif
|
||||
|
||||
// Order these assemblies by internal dependencies
|
||||
ImmutableArray<IAssemblyResourceInfo> resources;
|
||||
if (ignoreDependencySorting)
|
||||
@@ -243,12 +245,15 @@ public partial class PackageService : IPackageService
|
||||
}
|
||||
|
||||
// Try loading them, throw on failure.
|
||||
if (!_pluginService.Value.TryLoadAndInstanceTypes<IAssemblyPlugin>(resources, true, out var instancedTypes))
|
||||
if (_pluginService.Value.LoadAndInstanceTypes<IAssemblyPlugin>(resources, true, out var instancedTypes) is { IsFailed: true} failed2)
|
||||
{
|
||||
throw new TypeLoadException($"PackageService: unable to load assemblies for package {this.Package.Name}! Aborting loading!");
|
||||
return failed2.WithError(new Error($"{nameof(LoadPlugins)}: Failed to load plugins for {this.Package.Name}")
|
||||
.WithMetadata(MetadataType.ExceptionObject, this)
|
||||
.WithMetadata(MetadataType.RootObject, assembliesInfo));
|
||||
}
|
||||
|
||||
PluginsLoaded = true;
|
||||
return FluentResults.Result.Ok();
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -257,37 +262,28 @@ public partial class PackageService : IPackageService
|
||||
}
|
||||
}
|
||||
|
||||
public void LoadLocalizations([NotNull]ILocalizationsResourcesInfo localizationsInfo)
|
||||
public FluentResults.Result LoadLocalizations([NotNull]ILocalizationsResourcesInfo localizationsInfo)
|
||||
{
|
||||
_operationsUsageLock.EnterReadLock();
|
||||
LoadingOperationsRunning = true;
|
||||
try
|
||||
{
|
||||
if (IsDisposed)
|
||||
if (CheckResourceSanitation(OneOf<IAssembliesResourcesInfo, ILocalizationsResourcesInfo,
|
||||
IConfigsResourcesInfo, IConfigProfilesResourcesInfo, ILuaScriptsResourcesInfo>
|
||||
.FromT1(localizationsInfo)) is { IsFailed: true } failed)
|
||||
{
|
||||
throw new ObjectDisposedException($"This package service instance is disposed!");
|
||||
return failed;
|
||||
}
|
||||
|
||||
SanitationChecksCore(localizationsInfo, "localizations", nameof(LoadLocalizations));
|
||||
SanitationChecksEnumerable(localizationsInfo.Localizations, "localizations", nameof(LoadLocalizations));
|
||||
|
||||
#if DEBUG
|
||||
localizationsInfo.Localizations.ForEach(ri =>
|
||||
if (_localizationService.Value.LoadLocalizations(localizationsInfo.Localizations) is { IsFailed: true} failed2)
|
||||
{
|
||||
if (!this.Localizations.Contains(ri))
|
||||
{
|
||||
throw new ArgumentException(
|
||||
$"Package Service: tried to load the localization resource for package {this.Package.Name} but it is not in the list for this package.");
|
||||
}
|
||||
});
|
||||
#endif
|
||||
|
||||
if (!_localizationService.Value.TryLoadLocalizations(localizationsInfo.Localizations))
|
||||
{
|
||||
throw new FileLoadException($"Package Service: unable to load localizations for package {this.Package.Name}! Aborting!");
|
||||
return failed2.WithError(new Error($"{nameof(LoadLocalizations)}: Failed to load localizations")
|
||||
.WithMetadata(MetadataType.ExceptionObject, this)
|
||||
.WithMetadata(MetadataType.RootObject, localizationsInfo));
|
||||
}
|
||||
|
||||
LocalizationsLoaded = true;
|
||||
return FluentResults.Result.Ok();
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -296,38 +292,28 @@ public partial class PackageService : IPackageService
|
||||
}
|
||||
}
|
||||
|
||||
public void AddLuaScripts([NotNull]ILuaScriptsResourcesInfo luaScriptsInfo)
|
||||
public FluentResults.Result AddLuaScripts([NotNull]ILuaScriptsResourcesInfo luaScriptsInfo)
|
||||
{
|
||||
_operationsUsageLock.EnterReadLock();
|
||||
LoadingOperationsRunning = true;
|
||||
try
|
||||
{
|
||||
if (IsDisposed)
|
||||
if (CheckResourceSanitation(OneOf<IAssembliesResourcesInfo, ILocalizationsResourcesInfo,
|
||||
IConfigsResourcesInfo, IConfigProfilesResourcesInfo, ILuaScriptsResourcesInfo>
|
||||
.FromT4(luaScriptsInfo)) is { IsFailed: true } failed)
|
||||
{
|
||||
throw new ObjectDisposedException($"This package service instance is disposed!");
|
||||
return failed;
|
||||
}
|
||||
|
||||
SanitationChecksCore(luaScriptsInfo, "luaScripts", nameof(AddLuaScripts));
|
||||
SanitationChecksEnumerable(luaScriptsInfo.LuaScripts, "luaScripts", nameof(AddLuaScripts));
|
||||
|
||||
#if DEBUG
|
||||
luaScriptsInfo.LuaScripts.ForEach(ri =>
|
||||
if (_luaScriptService.Value.AddScriptFiles(luaScriptsInfo.LuaScripts) is { IsFailed: true} failed2)
|
||||
{
|
||||
if (!this.LuaScripts.Contains(ri))
|
||||
{
|
||||
throw new ArgumentException(
|
||||
$"Package Service: tried to load the lua script resource for package {this.Package.Name} but it is not in the list for this package.");
|
||||
}
|
||||
});
|
||||
#endif
|
||||
|
||||
if (!_luaScriptService.Value.TryAddScriptFiles(luaScriptsInfo.LuaScripts))
|
||||
{
|
||||
throw new ArgumentException(
|
||||
$"Package Service: unable to add lua files for package {this.Package.Name}! Aborting!");
|
||||
return failed2.WithError(new Error($"{nameof(LoadLocalizations)}: Failed to load lua scripts.")
|
||||
.WithMetadata(MetadataType.ExceptionObject, this)
|
||||
.WithMetadata(MetadataType.RootObject, luaScriptsInfo));
|
||||
}
|
||||
|
||||
LuaScriptsLoaded = true;
|
||||
return FluentResults.Result.Ok();
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -336,7 +322,7 @@ public partial class PackageService : IPackageService
|
||||
}
|
||||
}
|
||||
|
||||
public void LoadConfig(
|
||||
public FluentResults.Result LoadConfig(
|
||||
[NotNull]IConfigsResourcesInfo configsResourcesInfo,
|
||||
[NotNull]IConfigProfilesResourcesInfo configProfilesResourcesInfo)
|
||||
{
|
||||
@@ -344,48 +330,38 @@ public partial class PackageService : IPackageService
|
||||
LoadingOperationsRunning = true;
|
||||
try
|
||||
{
|
||||
if (IsDisposed)
|
||||
// register configs
|
||||
if (CheckResourceSanitation(OneOf<IAssembliesResourcesInfo, ILocalizationsResourcesInfo,
|
||||
IConfigsResourcesInfo, IConfigProfilesResourcesInfo, ILuaScriptsResourcesInfo>
|
||||
.FromT2(configsResourcesInfo)) is { IsFailed: true } failed)
|
||||
{
|
||||
throw new ObjectDisposedException($"This package service instance is disposed!");
|
||||
return failed;
|
||||
}
|
||||
|
||||
SanitationChecksCore(configsResourcesInfo, "config", nameof(LoadConfig));
|
||||
SanitationChecksCore(configProfilesResourcesInfo, "config profiles", nameof(LoadConfig));
|
||||
SanitationChecksEnumerable(configsResourcesInfo.Configs, "config", nameof(LoadConfig));
|
||||
SanitationChecksEnumerable(configProfilesResourcesInfo.ConfigProfiles, "config profiles", nameof(LoadConfig));
|
||||
|
||||
#if DEBUG
|
||||
configsResourcesInfo.Configs.ForEach(ri =>
|
||||
if (_configService.Value.AddConfigs(configsResourcesInfo.Configs) is { IsFailed: true} failed2)
|
||||
{
|
||||
if (!this.Configs.Contains(ri))
|
||||
{
|
||||
throw new ArgumentException(
|
||||
$"Package Service: tried to load the configs resource for package {this.Package.Name} but it is not in the list for this package.");
|
||||
}
|
||||
});
|
||||
|
||||
configProfilesResourcesInfo.ConfigProfiles.ForEach(ri =>
|
||||
{
|
||||
if (!this.ConfigProfiles.Contains(ri))
|
||||
{
|
||||
throw new ArgumentException(
|
||||
$"Package Service: tried to load the localization resource for package {this.Package.Name} but it is not in the list for this package.");
|
||||
}
|
||||
});
|
||||
#endif
|
||||
|
||||
if (!_configService.Value.TryAddConfigs(configsResourcesInfo.Configs))
|
||||
{
|
||||
throw new ArgumentException(
|
||||
$"Package Service: unable to add configs for package {this.Package.Name}! Aborting!");
|
||||
return failed2.WithError(new Error($"{nameof(LoadLocalizations)}: Failed to load configs.")
|
||||
.WithMetadata(MetadataType.ExceptionObject, this)
|
||||
.WithMetadata(MetadataType.RootObject, configsResourcesInfo));
|
||||
}
|
||||
|
||||
if (!_configService.Value.TryAddConfigsProfiles(configProfilesResourcesInfo.ConfigProfiles))
|
||||
// register config profiles
|
||||
if (CheckResourceSanitation(OneOf<IAssembliesResourcesInfo, ILocalizationsResourcesInfo,
|
||||
IConfigsResourcesInfo, IConfigProfilesResourcesInfo, ILuaScriptsResourcesInfo>
|
||||
.FromT3(configProfilesResourcesInfo)) is { IsFailed: true } failed3)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
$"Package Service: unable to add configs profiles for package {this.Package.Name}! Aborting!");
|
||||
return failed3;
|
||||
}
|
||||
|
||||
if (_configService.Value.AddConfigsProfiles(configProfilesResourcesInfo.ConfigProfiles) is { IsFailed: true} failed4)
|
||||
{
|
||||
return failed4.WithError(new Error($"{nameof(LoadLocalizations)}: Failed to load config profiles.")
|
||||
.WithMetadata(MetadataType.ExceptionObject, this)
|
||||
.WithMetadata(MetadataType.RootObject, configProfilesResourcesInfo));
|
||||
}
|
||||
|
||||
ConfigsLoaded = true;
|
||||
return FluentResults.Result.Ok();
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -463,22 +439,24 @@ public partial class PackageService : IPackageService
|
||||
}
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
public FluentResults.Result Reset()
|
||||
{
|
||||
_operationsUsageLock.EnterWriteLock();
|
||||
|
||||
try
|
||||
{
|
||||
if (this.Package is null)
|
||||
{
|
||||
_loggerService.LogError(
|
||||
$"Package Service: cannot Dispose of service as ContentPackage and info is not set!");
|
||||
return;
|
||||
return FluentResults.Result.Fail(new Error($"Package Service: cannot Dispose of service as ContentPackage and info is not set!")
|
||||
.WithMetadata(MetadataType.ExceptionDetails, nameof(Reset))
|
||||
.WithMetadata(MetadataType.ExceptionObject, this));
|
||||
}
|
||||
|
||||
if (this.ModConfigInfo is null)
|
||||
{
|
||||
_loggerService.LogError($"Package Service: cannot Dispose of service as ModConfigInfo is not loaded!");
|
||||
return;
|
||||
return FluentResults.Result.Fail(new Error($"Package Service: cannot Dispose of service as ModConfigInfo is not set!")
|
||||
.WithMetadata(MetadataType.ExceptionDetails, nameof(Reset))
|
||||
.WithMetadata(MetadataType.ExceptionObject, this));
|
||||
}
|
||||
|
||||
Interlocked.MemoryBarrier(); //ensure cache states
|
||||
@@ -491,7 +469,7 @@ public partial class PackageService : IPackageService
|
||||
_operationsUsageLock.EnterWriteLock();
|
||||
if (timeoutLimit < DateTime.Now)
|
||||
{
|
||||
_loggerService.LogError($"Package Service: Dispose() time out reached while waiting for other operations. Continuing.");
|
||||
_loggerService.LogError($"Package Service: Dispose() grace time-out reached while waiting for other operations. Continuing.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -520,6 +498,7 @@ public partial class PackageService : IPackageService
|
||||
_localizationService.Value.Remove(this.Localizations);
|
||||
LocalizationsLoaded = false;
|
||||
}
|
||||
return FluentResults.Result.Ok();
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -531,96 +510,176 @@ public partial class PackageService : IPackageService
|
||||
|
||||
#region INTERNAL
|
||||
|
||||
private void SanitationChecksCore(object o, string resTypeInfoName, string callerName)
|
||||
/// <summary>
|
||||
/// [Thread Unsafe] Performs sanitation and null checks on resources and returns the results.
|
||||
/// NOTE: Requires that resource locks be set by the caller.
|
||||
/// </summary>
|
||||
/// <param name="resourcesInfos"></param>
|
||||
/// <returns></returns>
|
||||
private FluentResults.Result CheckResourceSanitation(
|
||||
OneOf.OneOf<IAssembliesResourcesInfo, ILocalizationsResourcesInfo,
|
||||
IConfigsResourcesInfo, IConfigProfilesResourcesInfo, ILuaScriptsResourcesInfo> resourcesInfos)
|
||||
{
|
||||
if (o is null)
|
||||
// execute checks based on known types
|
||||
return resourcesInfos.Match<FluentResults.Result>(
|
||||
ass => ChecksDispatcher(ass, nameof(ass.Assemblies), nameof(LoadPlugins),
|
||||
ass.Assemblies, this.Assemblies),
|
||||
loc => ChecksDispatcher(loc, nameof(loc.Localizations), nameof(LoadLocalizations),
|
||||
loc.Localizations, this.Localizations),
|
||||
cfg => ChecksDispatcher(cfg, nameof(cfg.Configs), nameof(LoadConfig),
|
||||
cfg.Configs, this.Configs),
|
||||
cfp => ChecksDispatcher(cfp, nameof(cfp.ConfigProfiles), nameof(LoadConfig),
|
||||
cfp.ConfigProfiles, this.ConfigProfiles),
|
||||
lua => ChecksDispatcher(lua, nameof(lua.LuaScripts), nameof(AddLuaScripts),
|
||||
lua.LuaScripts, this.LuaScripts));
|
||||
|
||||
|
||||
/*
|
||||
* Helper functions
|
||||
*/
|
||||
FluentResults.Result ChecksDispatcher<T>(object obj, string resName, string callerName,
|
||||
ImmutableArray<T> resList, ImmutableArray<T> compareList)
|
||||
where T : class, IPackageInfo, IResourceInfo, IResourceCultureInfo, IPackageDependenciesInfo
|
||||
{
|
||||
_loggerService.LogError($"Package Service: {resTypeInfoName} resources list is null!");
|
||||
throw new NullReferenceException($"Package Service: {resTypeInfoName} resources list is null!");
|
||||
string errMsg = $"{callerName}: Failed to load {resName}.";
|
||||
if (DisposeCheck(obj) is { IsFailed: true } failed)
|
||||
return failed;
|
||||
if (SanitationChecksCore(obj, resName, callerName) is { IsFailed: true } failed1)
|
||||
return failed1.WithError(new Error(errMsg));
|
||||
if (SanitationChecksEnumerable(resList, resName, callerName) is { IsFailed: true } failed2)
|
||||
return failed2.WithError(new Error(errMsg));
|
||||
if (DebugCheck(resList, compareList, resName) is {IsFailed: true} failed3)
|
||||
return failed3.WithError(new Error(errMsg));
|
||||
return FluentResults.Result.Ok();
|
||||
}
|
||||
|
||||
FluentResults.Result DisposeCheck(object obj)
|
||||
{
|
||||
if (IsDisposed)
|
||||
{
|
||||
return FluentResults.Result.Fail(new Error($"{nameof(PackageService)}: Tried to load resources when disposed.")
|
||||
.WithMetadata(MetadataType.ExceptionObject, this)
|
||||
.WithMetadata(MetadataType.RootObject, obj));
|
||||
}
|
||||
return FluentResults.Result.Ok();
|
||||
}
|
||||
|
||||
FluentResults.Result DebugCheck<T>(ImmutableArray<T> resList, ImmutableArray<T> compareList, string resName)
|
||||
where T : class, IPackageInfo
|
||||
{
|
||||
#if DEBUG
|
||||
Stack<Error> errors = new();
|
||||
resList.ForEach(res =>
|
||||
{
|
||||
if (!compareList.Contains(res))
|
||||
{
|
||||
errors.Push(new Error($"Failed to load {resName} for: {this.Package.Name}")
|
||||
.WithMetadata(MetadataType.ExceptionDetails, $"Tries to load {resName} resource {res.InternalName} but it is not from this package!")
|
||||
.WithMetadata(MetadataType.ExceptionObject, this)
|
||||
.WithMetadata(MetadataType.RootObject, res));
|
||||
}
|
||||
});
|
||||
if (errors.Count > 0)
|
||||
{
|
||||
return FluentResults.Result.Fail(errors).WithError(
|
||||
new Error($"{nameof(LoadPlugins)}: errors in {resName} resources.")
|
||||
.WithMetadata(MetadataType.ExceptionObject, this)
|
||||
.WithMetadata(MetadataType.RootObject, this.Package));
|
||||
}
|
||||
#endif
|
||||
return FluentResults.Result.Ok();
|
||||
}
|
||||
}
|
||||
|
||||
private FluentResults.Result SanitationChecksCore(object obj, string resTypeInfoName, string callerName)
|
||||
{
|
||||
Error e = null;
|
||||
|
||||
if (obj is null)
|
||||
{
|
||||
e = new Error($"{nameof(SanitationChecksCore)}: null checks failed!")
|
||||
.WithMetadata(MetadataType.ExceptionDetails, "Object is null!")
|
||||
.WithMetadata(MetadataType.ExceptionObject, this)
|
||||
.WithMetadata(MetadataType.Sources, new List<string>() { resTypeInfoName, callerName });
|
||||
}
|
||||
|
||||
if (this.Package is null)
|
||||
{
|
||||
_loggerService.LogError($"Package Service: package not set at {callerName}()!");
|
||||
throw new NullReferenceException($"Package Service: package not set at {callerName}()!");
|
||||
e = (e ?? new Error($"{nameof(SanitationChecksCore)}: null checks failed!"))
|
||||
.WithMetadata(MetadataType.ExceptionDetails, "The Package is null!")
|
||||
.WithMetadata(MetadataType.ExceptionObject, this)
|
||||
.WithMetadata(MetadataType.Sources, new List<string>() { resTypeInfoName, callerName });
|
||||
}
|
||||
|
||||
return e is null ? FluentResults.Result.Ok() : FluentResults.Result.Fail(e);
|
||||
}
|
||||
|
||||
private void SanitationChecksEnumerable<T>(ImmutableArray<T> resourceInfos, string resTypeInfoName, string callerName) where T : IResourceInfo, IResourceCultureInfo, IPackageInfo, IPackageDependenciesInfo
|
||||
private FluentResults.Result SanitationChecksEnumerable<T>(ImmutableArray<T> resourceInfos, string resTypeInfoName, string callerName) where T : IResourceInfo, IResourceCultureInfo, IPackageInfo, IPackageDependenciesInfo
|
||||
{
|
||||
// Check if list is empty. Nothing more to do.
|
||||
if (resourceInfos.IsDefaultOrEmpty)
|
||||
return;
|
||||
return FluentResults.Result.Ok();
|
||||
|
||||
Stack<Error> errors = new();
|
||||
|
||||
// Check if all resources in the list are registered to this package, throw if not.
|
||||
foreach (var resourceInfo in resourceInfos)
|
||||
{
|
||||
// ownership checks
|
||||
if (resourceInfo.OwnerPackage is null)
|
||||
{
|
||||
throw new ArgumentException($"Package Service: {resTypeInfoName} info for resource does not have a package name set! Run by {this.Package.Name}.");
|
||||
{
|
||||
errors.Push(new Error($"Error for resource: {resTypeInfoName}. OwnerPackage is null!")
|
||||
.WithMetadata(MetadataType.ExceptionObject, this)
|
||||
.WithMetadata(MetadataType.RootObject, resourceInfo));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (resourceInfo.OwnerPackage != this.Package)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
$"Package Service: {resTypeInfoName} info does not belong to this package! Owned by {resourceInfo.OwnerPackage.Name} but is run by {this.Package.Name}.");
|
||||
errors.Push(new Error($"Error for resource: {resTypeInfoName}. $\"OwnerPackage {{resourceInfo.OwnerPackage?.Name}} is not the same as this package: {{this.Package}}")
|
||||
.WithMetadata(MetadataType.ExceptionObject, this)
|
||||
.WithMetadata(MetadataType.RootObject, resourceInfo));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if external dependencies are loaded and if current environment is supported, throw if not
|
||||
if (resourceInfo.Dependencies.IsDefaultOrEmpty)
|
||||
continue;
|
||||
|
||||
bool resourceMissing = false;
|
||||
|
||||
resourceInfo.Dependencies.ForEach(pdi =>
|
||||
|
||||
// ReSharper disable once ForeachCanBePartlyConvertedToQueryUsingAnotherGetEnumerator
|
||||
foreach (var pdi in resourceInfo.Dependencies)
|
||||
{
|
||||
// for clarification: assemblies passed to the function should always be loaded.
|
||||
// optional assemblies should be filtered out before the list is sent.
|
||||
// for clarification: all resources passed to the function should always be loaded.
|
||||
// unneeded optional resources should be filtered out before the list is sent.
|
||||
// left this as a reminder :)
|
||||
/*if (pdi.Optional)
|
||||
return;*/
|
||||
if (!_packageManagementService.CheckDependencyLoaded(pdi))
|
||||
{
|
||||
resourceMissing = true;
|
||||
_loggerService.LogError(
|
||||
$"Package Service: the following dependency for package {resourceInfo.OwnerPackage.Name} is not loaded: {pdi.DependencyPackage?.Name ?? (pdi.PackageName.IsNullOrWhiteSpace() ? pdi.SteamWorkshopId.ToString() : pdi.PackageName)}");
|
||||
errors.Push(new Error($"Dependency missing for resource: {resourceInfo.OwnerPackage.Name}")
|
||||
.WithMetadata(MetadataType.ExceptionDetails, $"Missing dependency: {pdi.DependencyPackage?.Name ?? (pdi.FallbackPackageName.IsNullOrWhiteSpace() ? pdi.SteamWorkshopId.ToString() : pdi.FallbackPackageName)}")
|
||||
.WithMetadata(MetadataType.ExceptionObject, this)
|
||||
.WithMetadata(MetadataType.RootObject, resourceInfo));
|
||||
}
|
||||
});
|
||||
|
||||
if (!resourceMissing)
|
||||
{
|
||||
throw new FileLoadException($"Package Service: dependencies for package {resourceInfo.OwnerPackage.Name} are not loaded.");
|
||||
}
|
||||
|
||||
// check runtime platform
|
||||
if (!_packageManagementService.CheckEnvironmentSupported(resourceInfo))
|
||||
{
|
||||
throw new PlatformNotSupportedException($"Package service: the {resTypeInfoName} from {resourceInfo.OwnerPackage.Name} is not supported on this platform.");
|
||||
errors.Push(new Error($"The resource {resourceInfo.OwnerPackage?.Name} does not support the current platform!")
|
||||
.WithMetadata(MetadataType.ExceptionObject, this)
|
||||
.WithMetadata(MetadataType.RootObject, resourceInfo));
|
||||
}
|
||||
|
||||
// check local culture
|
||||
if (!_localizationService.Value.IsCurrentCultureSupported(resourceInfo))
|
||||
{
|
||||
throw new PlatformNotSupportedException($"Package service: the {resTypeInfoName} from {resourceInfo.OwnerPackage.Name} is not supported in this culture.");
|
||||
errors.Push(new Error($"The resource {resourceInfo.OwnerPackage?.Name} does not support the current culture/region!")
|
||||
.WithMetadata(MetadataType.ExceptionObject, this)
|
||||
.WithMetadata(MetadataType.RootObject, resourceInfo));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static bool GetThreadSafeBool(ref int var) => Interlocked.CompareExchange(ref var, 1, 1) == 1;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void SetThreadSafeBool(ref int var, bool value)
|
||||
{
|
||||
if (value)
|
||||
{
|
||||
Interlocked.CompareExchange(ref var, 1, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
Interlocked.CompareExchange(ref var, 0, 1);
|
||||
}
|
||||
return errors.Count > 0 ? FluentResults.Result.Fail(errors) : FluentResults.Result.Ok();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
using System.Collections.Immutable;
|
||||
using Barotrauma.LuaCs.Data;
|
||||
using FluentResults;
|
||||
using Microsoft.CodeAnalysis;
|
||||
|
||||
namespace Barotrauma.LuaCs.Services;
|
||||
|
||||
public class PluginManagementService : IPluginManagementService
|
||||
{
|
||||
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public FluentResults.Result Reset()
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public bool IsAssemblyLoadedGlobal(string friendlyName)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public Result<ImmutableArray<T>> GetTypes<T>(ContentPackage package = null, string namespacePrefix = null, bool includeInterfaces = false,
|
||||
bool includeAbstractTypes = false, bool includeDefaultContext = true, bool includeExplicitAssembliesOnly = false)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public ImmutableArray<MetadataReference> GetStandardMetadataReferences()
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public ImmutableArray<MetadataReference> GetPluginMetadataReferences()
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public Result<ImmutableArray<IAssemblyResourceInfo>> GetCachedAssembliesForPackage(ContentPackage package)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public Result<ImmutableArray<IAssemblyResourceInfo>> LoadAssemblyResources(ImmutableArray<IAssemblyResourceInfo> resource)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
namespace Barotrauma.LuaCs.Services;
|
||||
|
||||
public interface IEventService
|
||||
public class PluginService
|
||||
{
|
||||
|
||||
}
|
||||
@@ -1,16 +1,17 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.LuaCs.Data;
|
||||
using FluentResults;
|
||||
|
||||
namespace Barotrauma.LuaCs.Services.Processing;
|
||||
|
||||
#region TypeDef
|
||||
|
||||
// ReSharper disable once TypeParameterCanBeVariant
|
||||
public interface IConverterService<TSrc, TOut> : IService
|
||||
public interface IConverterService<TSrc, TOut> : IReusableService
|
||||
{
|
||||
bool TryParseResource(TSrc src, out TOut resources);
|
||||
bool TryParseResources(IEnumerable<TSrc> sources, out List<TOut> resources);
|
||||
Result<TOut> TryParseResource(TSrc src);
|
||||
Result<TOut> TryParseResources(IEnumerable<TSrc> sources);
|
||||
}
|
||||
|
||||
public interface IXmlResourceConverterService<TOut> : IConverterService<XElement, TOut> { }
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
using Barotrauma.LuaCs.Data;
|
||||
|
||||
namespace Barotrauma.LuaCs.Services.Processing;
|
||||
|
||||
public interface IModConfigParserService : IReusableService
|
||||
{
|
||||
FluentResults.Result<IModConfigInfo> BuildConfigForPackage(ContentPackage package);
|
||||
FluentResults.Result<IModConfigInfo> BuildConfigFromManifest(string manifestPath);
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
namespace Barotrauma.LuaCs.Services.Safe;
|
||||
using Barotrauma.LuaCs.Configuration;
|
||||
|
||||
namespace Barotrauma.LuaCs.Services.Safe;
|
||||
|
||||
public interface ILuaConfigService : ILuaService
|
||||
{
|
||||
|
||||
@@ -1,6 +1,30 @@
|
||||
namespace Barotrauma.LuaCs.Services.Safe;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Barotrauma.LuaCs.Events;
|
||||
using Barotrauma.LuaCs.Services.Compatibility;
|
||||
|
||||
public interface ILuaEventService : ILuaService
|
||||
namespace Barotrauma.LuaCs.Services.Safe;
|
||||
|
||||
public interface ILuaSafeEventService : ILuaService, ILuaCsHook
|
||||
{
|
||||
|
||||
void Subscribe(string interfaceName, string identifier, IDictionary<string, LuaCsFunc> callbacks);
|
||||
/// <summary>
|
||||
/// Removes a subscriber from an event that subscribed under the given identifier.
|
||||
/// </summary>
|
||||
/// <param name="eventName"></param>
|
||||
/// <param name="identifier"></param>
|
||||
void Remove(string eventName, string identifier);
|
||||
/// <summary>
|
||||
/// Send an event to all subscribers to an interface.
|
||||
/// </summary>
|
||||
/// <param name="interfaceName">Name of the interface (must be registered with Lua).</param>
|
||||
/// <param name="runner">Execution runner, the subscriber is provided as the first argument in the lua runner.</param>
|
||||
/// <returns></returns>
|
||||
void PublishLuaEvent(string interfaceName, LuaCsFunc runner);
|
||||
}
|
||||
|
||||
public interface ILuaEventService : ILuaSafeEventService
|
||||
{
|
||||
public FluentResults.Result RegisterSafeEvent<T>() where T : IEvent<T>;
|
||||
public FluentResults.Result UnregisterSafeEvent<T>() where T : IEvent<T>;
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ public class ServicesProvider : IServicesProvider
|
||||
|
||||
private readonly ReaderWriterLockSlim _serviceLock = new();
|
||||
|
||||
public void RegisterServiceType<TSvcInterface, TService>(ServiceLifetime lifetime, ILifetime lifetimeInstance = null) where TSvcInterface : class, IService where TService : class, IService, TSvcInterface, new()
|
||||
public void RegisterServiceType<TSvcInterface, TService>(ServiceLifetime lifetime, ILifetime lifetimeInstance = null) where TSvcInterface : class, IService where TService : class, IService, TSvcInterface
|
||||
{
|
||||
if (lifetimeInstance is null)
|
||||
{
|
||||
@@ -50,7 +50,6 @@ public class ServicesProvider : IServicesProvider
|
||||
{
|
||||
_serviceLock.EnterReadLock();
|
||||
ServiceContainer.Register<TSvcInterface, TService>(lifetimeInstance);
|
||||
ServiceContainer.Compile<TService>();
|
||||
OnServiceRegistered?.Invoke(typeof(TSvcInterface), typeof(TService));
|
||||
}
|
||||
finally
|
||||
@@ -60,7 +59,7 @@ public class ServicesProvider : IServicesProvider
|
||||
}
|
||||
|
||||
public void RegisterServiceType<TSvcInterface, TService>(string name, ServiceLifetime lifetime,
|
||||
ILifetime lifetimeInstance = null) where TSvcInterface : class, IService where TService : class, IService, TSvcInterface, new()
|
||||
ILifetime lifetimeInstance = null) where TSvcInterface : class, IService where TService : class, IService, TSvcInterface
|
||||
{
|
||||
if (name.IsNullOrWhiteSpace())
|
||||
{
|
||||
@@ -91,7 +90,6 @@ public class ServicesProvider : IServicesProvider
|
||||
{
|
||||
_serviceLock.EnterReadLock();
|
||||
ServiceContainer.Register<TSvcInterface, TService>(name, lifetimeInstance);
|
||||
ServiceContainer.Compile<TService>();
|
||||
OnServiceRegistered?.Invoke(typeof(TSvcInterface), typeof(TService));
|
||||
}
|
||||
finally
|
||||
@@ -128,7 +126,7 @@ public class ServicesProvider : IServicesProvider
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryGetService<TSvcInterface>(out IService service) where TSvcInterface : class, IService
|
||||
public bool TryGetService<TSvcInterface>(out TSvcInterface service) where TSvcInterface : class, IService
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -147,7 +145,7 @@ public class ServicesProvider : IServicesProvider
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryGetService<TSvcInterface>(string name, out IService service) where TSvcInterface : class, IService
|
||||
public bool TryGetService<TSvcInterface>(string name, out TSvcInterface service) where TSvcInterface : class, IService
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
// ReSharper disable InconsistentNaming
|
||||
|
||||
namespace Barotrauma.LuaCs.Services;
|
||||
|
||||
public interface IAssemblyManagementService : IReusableService
|
||||
{
|
||||
/// <summary>
|
||||
/// Searches for an assembly given it's fully qualified name, while excluding the contexts with the given Guids, if supplied.
|
||||
/// </summary>
|
||||
/// <param name="assemblyName">The fully-qualified assembly name.</param>
|
||||
/// <param name="excludedContexts">Guids of excluded contexts.</param>
|
||||
/// <returns><b>On Success:</b> The assembly. <br/><b>On Failure:</b> nothing.</returns>
|
||||
FluentResults.Result<Assembly> GetLoadedAssembly(string assemblyName, in Guid[] excludedContexts);
|
||||
/// <summary>
|
||||
/// Searches for an assembly given it's fully qualified name, while excluding the contexts with the given Guids, if supplied.
|
||||
/// </summary>
|
||||
/// <param name="assemblyName">The assembly info.</param>
|
||||
/// <param name="excludedContexts">Guids of excluded contexts.</param>
|
||||
/// <returns><b>On Success:</b> The assembly. <br/><b>On Failure:</b> nothing.</returns>
|
||||
FluentResults.Result<Assembly> GetLoadedAssembly(AssemblyName assemblyName, in Guid[] excludedContexts);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the assembly <see cref="MetadataReference"/> collection for the BCL and base game assemblies.
|
||||
/// </summary>
|
||||
/// <returns><see cref="MetadataReference"/> collection, if any are found. Returns an empty collection otherwise.</returns>
|
||||
ImmutableArray<MetadataReference> GetDefaultMetadataReferences();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the assembly <see cref="MetadataReference"/> collection for all add-in assemblies loaded.
|
||||
/// </summary>
|
||||
/// <returns><see cref="MetadataReference"/> collection, if any are found. Returns an empty collection otherwise.</returns>
|
||||
ImmutableArray<MetadataReference> GetAddInContextsMetadataReferences();
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
ImmutableArray<IAssemblyLoaderService> AssemblyLoaderServices { get; }
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Barotrauma.LuaCs.Configuration;
|
||||
using Barotrauma.LuaCs.Data;
|
||||
using Barotrauma.LuaCs.Networking;
|
||||
using Barotrauma.LuaCs.Services.Safe;
|
||||
using Barotrauma.Networking;
|
||||
|
||||
namespace Barotrauma.LuaCs.Services;
|
||||
|
||||
public partial interface IConfigService : IReusableService, ILuaConfigService
|
||||
{
|
||||
/*
|
||||
* Resource Files.
|
||||
*/
|
||||
FluentResults.Result AddConfigs(ImmutableArray<IConfigResourceInfo> configResources);
|
||||
FluentResults.Result AddConfigsProfiles(ImmutableArray<IConfigProfileResourceInfo> configProfileResources);
|
||||
FluentResults.Result RemoveConfigs(ImmutableArray<IConfigResourceInfo> configResources);
|
||||
FluentResults.Result RemoveConfigsProfiles(ImmutableArray<IConfigProfileResourceInfo> configProfilesResources);
|
||||
|
||||
|
||||
/*
|
||||
* From resources
|
||||
*/
|
||||
FluentResults.Result AddConfigs(ImmutableArray<IConfigInfo> configs);
|
||||
FluentResults.Result AddConfigsProfiles(ImmutableArray<IConfigProfileInfo> configProfiles);
|
||||
FluentResults.Result RemoveConfigs(ImmutableArray<IConfigInfo> configs);
|
||||
FluentResults.Result RemoveConfigsProfiles(ImmutableArray<IConfigProfileInfo> configProfiles);
|
||||
|
||||
/*
|
||||
* Immediate mode
|
||||
*/
|
||||
FluentResults.Result<IConfigEntry<T>> AddConfigEntry<T>(ContentPackage package, string name,
|
||||
T defaultValue,
|
||||
NetSync syncMode = NetSync.None,
|
||||
ClientPermissions permissions = ClientPermissions.None,
|
||||
Func<T, bool> valueChangePredicate = null,
|
||||
Action<IConfigEntry<T>> onValueChanged = null) where T : IConvertible, IEquatable<T>;
|
||||
|
||||
FluentResults.Result<IConfigList> AddConfigList(ContentPackage package, string name,
|
||||
int defaultIndex, IReadOnlyList<string> values,
|
||||
NetSync syncMode = NetSync.None,
|
||||
ClientPermissions permissions = ClientPermissions.None,
|
||||
Func<IConfigList, int, bool> valueChangePredicate = null,
|
||||
Action<IConfigList, int> onValueChanged = null);
|
||||
|
||||
FluentResults.Result<IConfigRangeEntry<T>> AddConfigRangeEntry<T>(ContentPackage package, string name,
|
||||
T defaultValue, T minValue, T maxValue,
|
||||
Func<IConfigRangeEntry<T>, int> getStepCount,
|
||||
NetSync syncMode = NetSync.None,
|
||||
ClientPermissions permissions = ClientPermissions.None,
|
||||
Func<T, bool> valueChangePredicate = null,
|
||||
Action<IConfigEntry<T>> onValueChanged = null) where T : IConvertible, IEquatable<T>;
|
||||
|
||||
FluentResults.Result<IConfigEntry<T>> AddConfigEntry<T>(string packageName, string name,
|
||||
T defaultValue,
|
||||
NetSync syncMode = NetSync.None,
|
||||
ClientPermissions permissions = ClientPermissions.None,
|
||||
Func<T, bool> valueChangePredicate = null,
|
||||
Action<IConfigEntry<T>> onValueChanged = null) where T : IConvertible, IEquatable<T>;
|
||||
|
||||
FluentResults.Result<IConfigList> AddConfigList(string packageName, string name,
|
||||
int defaultIndex, IReadOnlyList<string> values,
|
||||
NetSync syncMode = NetSync.None,
|
||||
ClientPermissions permissions = ClientPermissions.None,
|
||||
Func<IConfigList, int, bool> valueChangePredicate = null,
|
||||
Action<IConfigList, int> onValueChanged = null);
|
||||
|
||||
FluentResults.Result<IConfigRangeEntry<T>> AddConfigRangeEntry<T>(string packageName, string name,
|
||||
T defaultValue, T minValue, T maxValue,
|
||||
Func<IConfigRangeEntry<T>, int> getStepCount,
|
||||
NetSync syncMode = NetSync.None,
|
||||
ClientPermissions permissions = ClientPermissions.None,
|
||||
Func<T, bool> valueChangePredicate = null,
|
||||
Action<IConfigEntry<T>> onValueChanged = null) where T : IConvertible, IEquatable<T>;
|
||||
|
||||
FluentResults.Result<IReadOnlyDictionary<string, IConfigBase>> GetConfigsForPackage(ContentPackage package);
|
||||
FluentResults.Result<IReadOnlyDictionary<string, IConfigBase>> GetConfigsForPackage(string packageName);
|
||||
IReadOnlyDictionary<(ContentPackage, string), IConfigBase> GetAllConfigs();
|
||||
FluentResults.Result<IConfigBase> GetConfig(ContentPackage package, string name);
|
||||
FluentResults.Result<IConfigBase> GetConfig(string packageName, string name);
|
||||
FluentResults.Result<T> GetConfig<T>(ContentPackage package, string name) where T : IConfigBase;
|
||||
FluentResults.Result<T> GetConfig<T>(string packageName, string name) where T : IConfigBase;
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using Barotrauma.LuaCs.Events;
|
||||
using Barotrauma.LuaCs.Services.Compatibility;
|
||||
using Barotrauma.LuaCs.Services.Safe;
|
||||
|
||||
namespace Barotrauma.LuaCs.Services;
|
||||
|
||||
public interface IEventService : IReusableService, ILuaEventService
|
||||
{
|
||||
FluentResults.Result SetLegacyLuaRunnerFactory<T>(Func<LuaCsFunc, T> runnerFactory) where T : IEvent<T>;
|
||||
void RemoveLegacyLuaRunnerFactory<T>() where T : IEvent<T>;
|
||||
void SetAliasToEvent<T>(string alias) where T : IEvent<T>;
|
||||
void RemoveEventAlias(string alias);
|
||||
void RemoveAllEventAliases<T>() where T : IEvent<T>;
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="subscriber"></param>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns></returns>
|
||||
FluentResults.Result Subscribe<T>(T subscriber) where T : IEvent<T>;
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="subscriber"></param>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
void Unsubscribe<T>(T subscriber) where T : IEvent;
|
||||
/// <summary>
|
||||
/// Clears all subscribers for a given event type and removes any registration to the type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The event type.</typeparam>
|
||||
void ClearAllEventSubscribers<T>() where T : IEvent;
|
||||
/// <summary>
|
||||
/// Clears all subscribers lists.
|
||||
/// </summary>
|
||||
void ClearAllSubscribers();
|
||||
/// <summary>
|
||||
/// Invokes all alive subscribers of the given event using the provided invocation factory.
|
||||
/// </summary>
|
||||
/// <param name="action"></param>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns></returns>
|
||||
FluentResults.Result PublishEvent<T>(Action<T> action) where T : IEvent<T>;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using Barotrauma.LuaCs.Data;
|
||||
|
||||
namespace Barotrauma.LuaCs.Services;
|
||||
|
||||
public interface ILocalizationService : IReusableService
|
||||
{
|
||||
IReadOnlyCollection<CultureInfo> GetLoadedLocales();
|
||||
void Remove(ImmutableArray<ILocalizationResourceInfo> localizations);
|
||||
FluentResults.Result SetCurrentCulture(CultureInfo culture);
|
||||
FluentResults.Result SetCurrentCulture(string cultureName);
|
||||
FluentResults.Result LoadLocalizations(ImmutableArray<ILocalizationResourceInfo> localizationResources);
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get a localized string without a fallback. Returns success/failure and associated data.
|
||||
/// </summary>
|
||||
/// <param name="key">Neutral localization key.</param>
|
||||
/// <returns></returns>
|
||||
FluentResults.Result<string> GetLocalizedString(string key);
|
||||
FluentResults.Result<string> GetLocalizedString(string key, CultureInfo targetCulture);
|
||||
string GetLocalizedString(string key, string fallback);
|
||||
string GetLocalizedString(string key, string fallback, CultureInfo targetCulture);
|
||||
FluentResults.Result<string> GetLocalizedStringForPackage(ContentPackage package, string key);
|
||||
FluentResults.Result<string> GetLocalizedStringForPackage(ContentPackage package, string key, CultureInfo targetCulture);
|
||||
string GetLocalizedStringForPackage(ContentPackage package, string key, string fallback);
|
||||
string GetLocalizedStringForPackage(ContentPackage package, string key, string fallback, CultureInfo targetCulture);
|
||||
FluentResults.Result RegisterLocalizationResolver(CultureInfo targetCulture, Func<string, CultureInfo, string> factoryResolver);
|
||||
bool IsCurrentCultureSupported(IResourceCultureInfo culturesInfo);
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using Barotrauma.Networking;
|
||||
using FluentResults;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Barotrauma.LuaCs.Services;
|
||||
@@ -7,14 +8,15 @@ namespace Barotrauma.LuaCs.Services;
|
||||
/// <summary>
|
||||
/// Provides console and debug logging services
|
||||
/// </summary>
|
||||
public interface ILoggerService : IService
|
||||
public interface ILoggerService : IReusableService
|
||||
{
|
||||
void HandleException(Exception exception, string prefix = null);
|
||||
void LogError(string message);
|
||||
void LogWarning(string message);
|
||||
void LogMessage(string message, Color? serverColor = null, Color? clientColor = null);
|
||||
void Log(string message, Color? color = null, ServerLog.MessageType messageType = ServerLog.MessageType.ServerMessage);
|
||||
|
||||
void LogResults(FluentResults.Result result);
|
||||
|
||||
#region DebugBuilds
|
||||
|
||||
void LogDebug(string message, Color? color = null);
|
||||
@@ -8,7 +8,7 @@ using MoonSharp.Interpreter.Interop;
|
||||
|
||||
namespace Barotrauma.LuaCs.Services;
|
||||
|
||||
public interface ILuaScriptService : IService
|
||||
public interface ILuaScriptService : IReusableService
|
||||
{
|
||||
#region Script_File_Collector
|
||||
|
||||
@@ -17,7 +17,8 @@ public interface ILuaScriptService : IService
|
||||
/// </summary>
|
||||
/// <param name="luaResource"></param>
|
||||
/// <returns></returns>
|
||||
bool TryAddScriptFiles(ImmutableArray<ILuaResourceInfo> luaResource);
|
||||
FluentResults.Result AddScriptFiles(ImmutableArray<ILuaResourceInfo> luaResource);
|
||||
|
||||
/// <summary>
|
||||
/// Removes the specific resources from the script runner. Important: Does not stop the
|
||||
/// execution of any code related to the files nor guarantee cleanup of resources!
|
||||
@@ -31,19 +32,20 @@ public interface ILuaScriptService : IService
|
||||
/// <param name="pauseExecutionOnScriptError"></param>
|
||||
/// <param name="verboseLogging"></param>
|
||||
/// <returns></returns>
|
||||
bool TryExecuteScripts(bool pauseExecutionOnScriptError = false, bool verboseLogging = false);
|
||||
FluentResults.Result ExecuteScripts(bool pauseExecutionOnScriptError = false, bool verboseLogging = false);
|
||||
|
||||
ImmutableArray<ILuaResourceInfo> GetScriptResources();
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
public interface ILuaScriptManagementService : IService
|
||||
public interface ILuaScriptManagementService : IReusableService
|
||||
{
|
||||
#region Script_File_Execution
|
||||
|
||||
bool TryExecuteLoadedScripts(ContentPackage package, bool pauseExecutionOnError = false, bool verboseLogging = false);
|
||||
bool TryExecuteLoadedScripts(ImmutableArray<ILuaResourceInfo> scripts, bool pauseExecutionOnError = false, bool verboseLogging = false);
|
||||
bool TryExecuteLoadedScripts(bool pauseExecutionOnError = false, bool verboseLogging = false);
|
||||
FluentResults.Result ExecuteLoadedScripts(ContentPackage package, bool pauseExecutionOnError = false, bool verboseLogging = false);
|
||||
FluentResults.Result ExecuteLoadedScripts(ImmutableArray<ILuaResourceInfo> scripts, bool pauseExecutionOnError = false, bool verboseLogging = false);
|
||||
FluentResults.Result ExecuteLoadedScripts(bool pauseExecutionOnError = false, bool verboseLogging = false);
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
using System;
|
||||
using Barotrauma.LuaCs.Data;
|
||||
using Barotrauma.LuaCs.Networking;
|
||||
using Barotrauma.LuaCs.Services.Compatibility;
|
||||
using Barotrauma.Networking;
|
||||
|
||||
namespace Barotrauma.LuaCs.Services;
|
||||
|
||||
internal delegate void NetMessageReceived(IReadMessage netMessage);
|
||||
|
||||
internal interface INetworkingService : IReusableService, ILuaCsNetworking
|
||||
{
|
||||
bool IsActive { get; }
|
||||
bool IsSynchronized { get; }
|
||||
|
||||
public INetWriteMessage Start(Guid netId);
|
||||
public void Receive(Guid netId, NetMessageReceived action);
|
||||
#if SERVER
|
||||
public void Send(IWriteMessage netMessage, NetworkConnection connection = null, DeliveryMethod deliveryMethod = DeliveryMethod.Reliable);
|
||||
#elif CLIENT
|
||||
public void Send(IWriteMessage netMessage, DeliveryMethod deliveryMethod = DeliveryMethod.Reliable);
|
||||
#endif
|
||||
public void RegisterNetVar(INetVar netVar);
|
||||
public void SendNetVar(INetVar netVar);
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.LuaCs.Data;
|
||||
|
||||
namespace Barotrauma.LuaCs.Services;
|
||||
|
||||
public interface IPackageManagementService : IReusableService
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds packages to the queue of loadable packages without initializing them.
|
||||
/// </summary>
|
||||
/// <param name="packages"></param>
|
||||
void QueuePackages(ImmutableArray<LoadablePackage> packages);
|
||||
|
||||
/// <summary>
|
||||
/// Generates the ModConfigInfo for all queued packages and adds them to the store.
|
||||
/// </summary>
|
||||
/// <param name="loadParallel">Use multithreaded loading.</param>
|
||||
/// <param name="reportFailOnDuplicates">Whether duplicate packages should be reported as errors.</param>
|
||||
/// <returns>Failure/Success records for each package.</returns>
|
||||
FluentResults.Result ParseQueuedPackages(bool loadParallel = true, bool reportFailOnDuplicates = false);
|
||||
/// <summary>
|
||||
/// Loads only the localizations, configs, and config profiles for stored packages.
|
||||
/// </summary>
|
||||
/// <param name="loadParallel"></param>
|
||||
/// <returns></returns>
|
||||
FluentResults.Result LoadPackageConfigsResourcesGroup(bool loadParallel = true);
|
||||
/// <summary>
|
||||
/// Loads all resources for stored packages.
|
||||
/// </summary>
|
||||
/// <param name="loadParallel">Use multithreaded loading.</param>
|
||||
/// <param name="safeResourcesOnly">Only load safe scripting resources, such as Lua. C# plugins disabled.</param>
|
||||
/// <returns></returns>
|
||||
FluentResults.Result LoadAllPackageResources(bool loadParallel = true, bool safeResourcesOnly = true);
|
||||
FluentResults.Result UnloadPackages();
|
||||
bool IsPackageLoaded(ContentPackage package);
|
||||
bool CheckDependencyLoaded(IPackageDependencyInfo info);
|
||||
bool CheckDependenciesLoaded([NotNull]IEnumerable<IPackageDependencyInfo> infos, out ImmutableArray<IPackageDependencyInfo> missingPackages);
|
||||
bool CheckEnvironmentSupported(IPlatformInfo platform);
|
||||
/// <summary>
|
||||
/// Tries to get the package dependency record to refer to that specific package if it exists, optionally create it.
|
||||
/// </summary>
|
||||
/// <param name="package">ContentPackage reference</param>
|
||||
/// <param name="addIfMissing">Register a new IPackageDependencyInfo reference.</param>
|
||||
/// <returns></returns>
|
||||
FluentResults.Result<IPackageDependencyInfo> GetPackageDependencyInfoRecord(ContentPackage package,
|
||||
bool addIfMissing = false);
|
||||
/// <summary>
|
||||
/// Tries to get the package dependency record to refer to that specific package if it exists, optionally create it.
|
||||
/// </summary>
|
||||
/// <param name="steamWorkshopId">The Steam Workshop ID, if available, if not enter zero ('0').</param>
|
||||
/// <param name="packageName">The name of the package.</param>
|
||||
/// <param name="folderPath">The folder path, as formatted in [ContentPackage.Path].</param>
|
||||
/// <param name="addIfMissing">Register a new IPackageDependencyInfo reference.</param>
|
||||
/// <returns></returns>
|
||||
FluentResults.Result<IPackageDependencyInfo> GetPackageDependencyInfoRecord(ulong steamWorkshopId,
|
||||
string packageName, string folderPath = null, bool addIfMissing = false);
|
||||
/// <summary>
|
||||
/// Tries to get the package dependency record to refer to that specific package if it exists.
|
||||
/// Note: This overload does not allow the registration of a new dependency.
|
||||
/// </summary>
|
||||
/// <param name="folderPath">The folder path, as formatted in [ContentPackage.Path].</param>
|
||||
/// <returns></returns>
|
||||
FluentResults.Result<IPackageDependencyInfo> GetPackageDependencyInfoRecord(string folderPath);
|
||||
|
||||
IPackageDependencyInfo CreateOrphanPackageDependencyInfoRecord(string packageName,
|
||||
string packagePath, ulong steamWorkshopId);
|
||||
}
|
||||
|
||||
public readonly record struct LoadablePackage
|
||||
{
|
||||
public ContentPackage Package { get; }
|
||||
public bool IsEnabled { get; }
|
||||
|
||||
public LoadablePackage(ContentPackage package, bool isEnabled)
|
||||
{
|
||||
Package = package;
|
||||
IsEnabled = isEnabled;
|
||||
}
|
||||
|
||||
public static ImmutableArray<LoadablePackage> FromEnumerable(IEnumerable<ContentPackage> packages, bool isEnabled)
|
||||
{
|
||||
var builder = ImmutableArray.CreateBuilder<LoadablePackage>();
|
||||
packages.ForEach(p => builder.Add(new LoadablePackage(p, isEnabled)));
|
||||
return builder.ToImmutable();
|
||||
}
|
||||
}
|
||||
@@ -3,21 +3,23 @@ using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Barotrauma.LuaCs.Data;
|
||||
using FluentResults;
|
||||
|
||||
namespace Barotrauma.LuaCs.Services;
|
||||
|
||||
public interface IPackageService : IService,
|
||||
public interface IPackageService : IReusableService,
|
||||
// These allow us the pass the IContentPackageService to anything that needs the data without having to directly reference the member
|
||||
IResourceCultureInfo, IAssembliesResourcesInfo, ILocalizationsResourcesInfo, ILuaScriptsResourcesInfo
|
||||
{
|
||||
ContentPackage Package { get; }
|
||||
IModConfigInfo ModConfigInfo { get; }
|
||||
bool IsEnabledInModList { get; }
|
||||
/// <summary>
|
||||
/// Try to load the XML config and resources information from the given package.
|
||||
/// </summary>
|
||||
/// <param name="package"></param>
|
||||
/// <returns>Whether the package was parsed without errors and any information was found. Will return false for purely vanilla packages.</returns>
|
||||
bool TryLoadResourcesInfo([NotNull]ContentPackage package);
|
||||
/// <returns>Whether the package was parsed without errors.</returns>
|
||||
FluentResults.Result LoadResourcesInfo([NotNull]LoadablePackage package);
|
||||
/// <summary>
|
||||
/// Tries to load all assemblies and instance plugins for the given resources list, regardless whether they're marked as optional and/or lazy load.
|
||||
/// Will sort by load priority unless overriden/bypassed.
|
||||
@@ -25,12 +27,12 @@ public interface IPackageService : IService,
|
||||
/// <param name="assembliesInfo"></param>
|
||||
/// <param name="ignoreDependencySorting"></param>
|
||||
/// <returns>Whether loading is successful. Returns true on an empty list.</returns>
|
||||
void LoadPlugins([NotNull]IAssembliesResourcesInfo assembliesInfo, bool ignoreDependencySorting = false);
|
||||
void LoadLocalizations([NotNull]ILocalizationsResourcesInfo localizationsInfo);
|
||||
void AddLuaScripts([NotNull]ILuaScriptsResourcesInfo luaScriptsInfo);
|
||||
FluentResults.Result LoadPlugins([NotNull]IAssembliesResourcesInfo assembliesInfo, bool ignoreDependencySorting = false);
|
||||
FluentResults.Result LoadLocalizations([NotNull]ILocalizationsResourcesInfo localizationsInfo);
|
||||
FluentResults.Result AddLuaScripts([NotNull]ILuaScriptsResourcesInfo luaScriptsInfo);
|
||||
#if CLIENT
|
||||
void LoadStyles([NotNull]IStylesResourcesInfo stylesInfo);
|
||||
FluentResults.Result LoadStyles([NotNull]IStylesResourcesInfo stylesInfo);
|
||||
#endif
|
||||
void LoadConfig([NotNull]IConfigsResourcesInfo configsResourcesInfo, [NotNull]IConfigProfilesResourcesInfo configProfilesResourcesInfo);
|
||||
FluentResults.Result LoadConfig([NotNull]IConfigsResourcesInfo configsResourcesInfo, [NotNull]IConfigProfilesResourcesInfo configProfilesResourcesInfo);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
using System;
|
||||
using System.Collections.Immutable;
|
||||
using System.Reflection;
|
||||
using Barotrauma.LuaCs.Data;
|
||||
using Microsoft.CodeAnalysis;
|
||||
|
||||
namespace Barotrauma.LuaCs.Services;
|
||||
|
||||
public interface IPluginManagementService : IReusableService
|
||||
{
|
||||
/// <summary>
|
||||
/// Checks if an assembly with either the fully-qualified name globally or a 'friendly name' within loaded plugins
|
||||
/// with the given name is loaded.
|
||||
/// </summary>
|
||||
/// <param name="friendlyName"></param>
|
||||
/// <returns></returns>
|
||||
bool IsAssemblyLoadedGlobal(string friendlyName);
|
||||
|
||||
// TODO: Documentation.
|
||||
FluentResults.Result<ImmutableArray<T>> GetTypes<T>(
|
||||
ContentPackage package = null,
|
||||
string namespacePrefix = null,
|
||||
bool includeInterfaces = false,
|
||||
bool includeAbstractTypes = false,
|
||||
bool includeDefaultContext = true,
|
||||
bool includeExplicitAssembliesOnly = false);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the assembly <c>MetadataReference</c> collection for the BCL and base game assemblies.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
ImmutableArray<MetadataReference> GetStandardMetadataReferences();
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
ImmutableArray<MetadataReference> GetPluginMetadataReferences();
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="package"></param>
|
||||
/// <returns></returns>
|
||||
FluentResults.Result<ImmutableArray<IAssemblyResourceInfo>> GetCachedAssembliesForPackage(ContentPackage package);
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="resource"></param>
|
||||
/// <returns>Success/Failure and list of failed resources, if any.</returns>
|
||||
FluentResults.Result<ImmutableArray<IAssemblyResourceInfo>> LoadAssemblyResources(ImmutableArray<IAssemblyResourceInfo> resource);
|
||||
}
|
||||
@@ -6,7 +6,7 @@ using Barotrauma.LuaCs.Data;
|
||||
|
||||
namespace Barotrauma.LuaCs.Services;
|
||||
|
||||
public interface IPluginService : IService
|
||||
public interface IPluginService : IReusableService
|
||||
{
|
||||
bool IsAssemblyLoaded(string friendlyName);
|
||||
/// <summary>
|
||||
@@ -17,21 +17,21 @@ public interface IPluginService : IService
|
||||
/// <param name="typeInstances"></param>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns></returns>
|
||||
bool TryLoadAndInstanceTypes<T>(IEnumerable<IAssemblyResourceInfo> assemblyResourcesInfo, bool injectServices, out ImmutableArray<T> typeInstances) where T : class, IAssemblyPlugin;
|
||||
ImmutableArray<T> GetLoadedPluginTypesInPackage<T>() where T : class, IAssemblyPlugin;
|
||||
FluentResults.Result LoadAndInstanceTypes<T>(IEnumerable<IAssemblyResourceInfo> assemblyResourcesInfo, bool injectServices, out ImmutableArray<T> typeInstances) where T : class, IAssemblyPlugin;
|
||||
FluentResults.Result<ImmutableArray<T>> GetLoadedPluginTypesInPackage<T>() where T : class, IAssemblyPlugin;
|
||||
/// <summary>
|
||||
/// Advances the loading/execution state of the plugin. IMPORTANT: You cannot set the execution state of plugins
|
||||
/// to 'Disposed'. You must instead call the 'DisposePlugins' method.
|
||||
/// </summary>
|
||||
/// <param name="newState"></param>
|
||||
/// <returns></returns>
|
||||
bool AdvancePluginStates(PluginRunState newState);
|
||||
FluentResults.Result AdvancePluginStates(PluginRunState newState);
|
||||
|
||||
/// <summary>
|
||||
/// Disposes of all running plugins hosted by the service and releases their references to allow unloading.
|
||||
/// </summary>
|
||||
/// <returns>Success of the operation. Returns false if any plugin threw errors during disposal.</returns>
|
||||
bool DisposePlugins();
|
||||
FluentResults.Result DisposePlugins();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current plugin execution state.
|
||||
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
|
||||
namespace Barotrauma.LuaCs.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Defines a service that can be reset to it's post-constructor state and reused without needing to be disposed.
|
||||
/// Intended for persistent services.
|
||||
/// </summary>
|
||||
public interface IReusableService : IService
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns the service to its original state (post-instantiation).
|
||||
/// Allows a service instance to be reused without disposing of the instance.
|
||||
/// </summary>
|
||||
FluentResults.Result Reset();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Base interface inherited by all services.
|
||||
/// </summary>
|
||||
public interface IService : IDisposable
|
||||
{
|
||||
bool IsDisposed { get; }
|
||||
public void CheckDisposed()
|
||||
{
|
||||
if (IsDisposed)
|
||||
throw new ObjectDisposedException($"Tried to call method on disposed object '{this.GetType().Name}'!");
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,7 @@ public interface IServicesProvider
|
||||
/// <param name="lifetimeInstance"></param>
|
||||
/// <typeparam name="TSvcInterface"></typeparam>
|
||||
/// <typeparam name="TService"></typeparam>
|
||||
void RegisterServiceType<TSvcInterface, TService>(ServiceLifetime lifetime, ILifetime lifetimeInstance = null) where TSvcInterface : class, IService where TService : class, IService, TSvcInterface, new();
|
||||
void RegisterServiceType<TSvcInterface, TService>(ServiceLifetime lifetime, ILifetime lifetimeInstance = null) where TSvcInterface : class, IReusableService where TService : class, IReusableService, TSvcInterface;
|
||||
|
||||
/// <summary>
|
||||
/// Registers a type as a service for a given interface that can be requested by name.
|
||||
@@ -29,7 +29,7 @@ public interface IServicesProvider
|
||||
/// <param name="lifetimeInstance"></param>
|
||||
/// <typeparam name="TSvcInterface"></typeparam>
|
||||
/// <typeparam name="TService"></typeparam>
|
||||
void RegisterServiceType<TSvcInterface, TService>(string name, ServiceLifetime lifetime, ILifetime lifetimeInstance = null) where TSvcInterface : class, IService where TService : class, IService, TSvcInterface, new();
|
||||
void RegisterServiceType<TSvcInterface, TService>(string name, ServiceLifetime lifetime, ILifetime lifetimeInstance = null) where TSvcInterface : class, IReusableService where TService : class, IReusableService, TSvcInterface;
|
||||
|
||||
/// <summary>
|
||||
/// Called whenever a new service type for a given interface is implemented.
|
||||
@@ -61,7 +61,7 @@ public interface IServicesProvider
|
||||
/// <param name="lifetime"></param>
|
||||
/// <typeparam name="TSvcInterface"></typeparam>
|
||||
/// <returns></returns>
|
||||
bool TryGetService<TSvcInterface>(out IService service) where TSvcInterface : class, IService;
|
||||
bool TryGetService<TSvcInterface>(out TSvcInterface service) where TSvcInterface : class, IReusableService;
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get a service for the given name and interface, returns success/failure.
|
||||
@@ -71,14 +71,14 @@ public interface IServicesProvider
|
||||
/// <param name="lifetime"></param>
|
||||
/// <typeparam name="TSvcInterface"></typeparam>
|
||||
/// <returns></returns>
|
||||
bool TryGetService<TSvcInterface>(string name, out IService service) where TSvcInterface : class, IService;
|
||||
bool TryGetService<TSvcInterface>(string name, out TSvcInterface service) where TSvcInterface : class, IReusableService;
|
||||
|
||||
/// <summary>
|
||||
/// Called whenever a new service is created/instanced.
|
||||
/// Args[0]: The interface type of the service.
|
||||
/// Args[1]: The instance of the service.
|
||||
/// </summary>
|
||||
event System.Action<Type, IService> OnServiceInstanced;
|
||||
event System.Action<Type, IReusableService> OnServiceInstanced;
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -89,7 +89,7 @@ public interface IServicesProvider
|
||||
/// </summary>
|
||||
/// <typeparam name="TSvc"></typeparam>
|
||||
/// <returns></returns>
|
||||
ImmutableArray<TSvc> GetAllServices<TSvc>() where TSvc : class, IService;
|
||||
ImmutableArray<TSvc> GetAllServices<TSvc>() where TSvc : class, IReusableService;
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.LuaCs.Services;
|
||||
|
||||
public interface IStorageService : IReusableService
|
||||
{
|
||||
#region LocalGameData
|
||||
|
||||
FluentResults.Result<XDocument> LoadLocalXml(ContentPackage package, string localFilePath);
|
||||
FluentResults.Result<byte[]> LoadLocalBinary(ContentPackage package, string localFilePath);
|
||||
FluentResults.Result<string> LoadLocalText(ContentPackage package, string localFilePath);
|
||||
FluentResults.Result<bool> FileExistsInLocalData(ContentPackage package, string localFilePath);
|
||||
|
||||
#endregion
|
||||
|
||||
#region ContentPackageData
|
||||
FluentResults.Result<XDocument> LoadPackageXml(ContentPackage package, string localFilePath, out XDocument document);
|
||||
FluentResults.Result<byte[]> LoadPackageBinary(ContentPackage package, string localFilePath, out byte[] bytes);
|
||||
FluentResults.Result<string> LoadPackageText(ContentPackage package, string localFilePath, out string text);
|
||||
|
||||
FluentResults.Result<ImmutableArray<XDocument>> LoadPackageXmlFiles(ContentPackage package, ImmutableArray<string> localFilePath);
|
||||
FluentResults.Result<ImmutableArray<byte[]>> TryLoadPackageBinaryFiles(ContentPackage package, ImmutableArray<string> localFilePath);
|
||||
FluentResults.Result<ImmutableArray<string>> TryLoadPackageTextFiles(ContentPackage package, ImmutableArray<string> localFilePath);
|
||||
|
||||
FluentResults.Result<ImmutableArray<string>> FindFilesInPackage(ContentPackage package, string localSubfolder, string regexFilter, bool searchRecursively);
|
||||
FluentResults.Result<bool> FileExistsInPackage(ContentPackage package, string localFilePath);
|
||||
|
||||
#endregion
|
||||
|
||||
#region AbsolutePaths
|
||||
|
||||
FluentResults.Result<XDocument> TryLoadXml(string filePatht);
|
||||
FluentResults.Result TrySaveXml(string filePath, in XDocument document);
|
||||
FluentResults.Result<byte[]> TryLoadBinary(string filePath);
|
||||
FluentResults.Result TrySaveBinary(string filePath, in byte[] bytes);
|
||||
FluentResults.Result<string> TryLoadText(string filePath);
|
||||
FluentResults.Result TrySaveText(string filePath, string text);
|
||||
FluentResults.Result<bool> FileExists(string filePath);
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,428 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Dynamic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.Loader;
|
||||
using System.Threading;
|
||||
using Barotrauma.LuaCs;
|
||||
using Barotrauma.LuaCs.Events;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Basic.Reference.Assemblies;
|
||||
using FluentResults;
|
||||
using FluentResults.LuaCs;
|
||||
using LightInject;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using OneOf;
|
||||
using Path = Barotrauma.IO.Path;
|
||||
|
||||
[assembly: InternalsVisibleTo(IAssemblyLoaderService.InternalsAwareAssemblyName)]
|
||||
|
||||
namespace Barotrauma.LuaCs.Services;
|
||||
public sealed class AssemblyLoader : AssemblyLoadContext, IAssemblyLoaderService
|
||||
{
|
||||
public Guid Id { get; init; }
|
||||
public bool IsReferenceOnlyMode { get; init; }
|
||||
public bool IsDisposed
|
||||
{
|
||||
get => ModUtils.Threading.GetBool(ref _isDisposed);
|
||||
private set => ModUtils.Threading.SetBool(ref _isDisposed, value);
|
||||
}
|
||||
private int _isDisposed;
|
||||
|
||||
//internal
|
||||
private readonly IAssemblyManagementService _assemblyManagementService;
|
||||
private readonly IEventService _eventService;
|
||||
private readonly Action<AssemblyLoader> _onUnload;
|
||||
/// <summary>
|
||||
/// This lock is just to ensure that we do not load while disposing
|
||||
/// </summary>
|
||||
private readonly ReaderWriterLockSlim _operationsLock = new(LockRecursionPolicy.SupportsRecursion);
|
||||
private readonly ConcurrentDictionary<string, AssemblyDependencyResolver> _dependencyResolvers = new();
|
||||
private readonly ConcurrentDictionary<AssemblyOrStringKey, AssemblyData> _loadedAssemblyData = new();
|
||||
|
||||
private ThreadLocal<bool> _isResolving = new(static()=>false); // cyclic resolution exit
|
||||
|
||||
#region PublicAPI
|
||||
|
||||
public AssemblyLoader(IAssemblyManagementService assemblyManagementService,
|
||||
IEventService eventService,
|
||||
Guid id, string name,
|
||||
bool isReferenceOnlyMode, Action<AssemblyLoader> onUnload)
|
||||
: base(isCollectible: true, name: name)
|
||||
{
|
||||
_assemblyManagementService = assemblyManagementService;
|
||||
_eventService = eventService;
|
||||
Id = id;
|
||||
IsReferenceOnlyMode = isReferenceOnlyMode;
|
||||
_onUnload = onUnload;
|
||||
if (_onUnload is not null)
|
||||
{
|
||||
base.Unloading += OnUnload;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public FluentResults.Result AddDependencyPaths(ImmutableArray<string> paths)
|
||||
{
|
||||
if (paths.Length == 0)
|
||||
return FluentResults.Result.Ok();
|
||||
var res = new FluentResults.Result();
|
||||
foreach (var path in paths)
|
||||
{
|
||||
try
|
||||
{
|
||||
var p = Path.GetFullPath(path.CleanUpPath());
|
||||
_dependencyResolvers[p] = new AssemblyDependencyResolver(p);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return res.WithError(new ExceptionalError(ex)
|
||||
.WithMetadata(MetadataType.Sources, path));
|
||||
}
|
||||
}
|
||||
return FluentResults.Result.Ok();
|
||||
}
|
||||
|
||||
public FluentResults.Result<Assembly> CompileScriptAssembly(
|
||||
[NotNull] string assemblyName,
|
||||
bool compileWithInternalAccess,
|
||||
ImmutableArray<SyntaxTree> syntaxTrees,
|
||||
ImmutableArray<MetadataReference> metadataReferences,
|
||||
CSharpCompilationOptions compilationOptions = null)
|
||||
{
|
||||
if (assemblyName.IsNullOrWhiteSpace())
|
||||
{
|
||||
return new FluentResults.Result<Assembly>().WithError(new Error($"The name provided is null!")
|
||||
.WithMetadata(MetadataType.ExceptionObject, this)
|
||||
.WithMetadata(MetadataType.RootObject, syntaxTrees));
|
||||
}
|
||||
|
||||
if (_loadedAssemblyData.ContainsKey(assemblyName))
|
||||
{
|
||||
return new FluentResults.Result<Assembly>().WithError(new Error($"The name provided is already assigned to an assembly!")
|
||||
.WithMetadata(MetadataType.ExceptionObject, this)
|
||||
.WithMetadata(MetadataType.RootObject, syntaxTrees));
|
||||
}
|
||||
|
||||
var compilationAssemblyName = compileWithInternalAccess ? IAssemblyLoaderService.InternalsAwareAssemblyName : assemblyName;
|
||||
|
||||
compilationOptions ??= new CSharpCompilationOptions(
|
||||
outputKind: OutputKind.DynamicallyLinkedLibrary,
|
||||
optimizationLevel: OptimizationLevel.Release,
|
||||
concurrentBuild: true,
|
||||
reportSuppressedDiagnostics: true,
|
||||
allowUnsafe: true);
|
||||
|
||||
if (!compileWithInternalAccess)
|
||||
{
|
||||
typeof(CSharpCompilationOptions)
|
||||
.GetProperty("TopLevelBinderFlags", BindingFlags.Instance | BindingFlags.NonPublic)
|
||||
?.SetValue(compilationOptions, (uint)1 << 22);
|
||||
}
|
||||
|
||||
using var asmMemoryStream = new MemoryStream();
|
||||
var result = CSharpCompilation.Create(compilationAssemblyName, syntaxTrees, metadataReferences, compilationOptions).Emit(asmMemoryStream);
|
||||
if (!result.Success)
|
||||
{
|
||||
var res = new FluentResults.Result().WithError(
|
||||
new Error($"Compilation failed for assembly {assemblyName}!"));
|
||||
var failuresDiag = result.Diagnostics.Where(d => d.IsWarningAsError || d.Severity == DiagnosticSeverity.Error);
|
||||
foreach (var diag in failuresDiag)
|
||||
{
|
||||
res = res.WithError(new Error(diag.GetMessage())
|
||||
.WithMetadata(MetadataType.ExceptionObject, this)
|
||||
.WithMetadata(MetadataType.ExceptionDetails, diag.Descriptor.Description));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
asmMemoryStream.Seek(0, SeekOrigin.Begin);
|
||||
try
|
||||
{
|
||||
var data = new AssemblyData(LoadFromStream(asmMemoryStream), asmMemoryStream.ToArray());
|
||||
_loadedAssemblyData[data.Assembly] = data;
|
||||
return new FluentResults.Result<Assembly>().WithSuccess($"Compiled assembly {assemblyName} successful.").WithValue(data.Assembly);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new FluentResults.Result().WithError(new ExceptionalError(ex));
|
||||
}
|
||||
}
|
||||
|
||||
public FluentResults.Result<Assembly> LoadAssemblyFromFile(string assemblyFilePath,
|
||||
ImmutableArray<string> additionalDependencyPaths)
|
||||
{
|
||||
if (assemblyFilePath.IsNullOrWhiteSpace())
|
||||
return new FluentResults.Result<Assembly>().WithError(new Error($"The path provided is null!"));
|
||||
|
||||
if (additionalDependencyPaths.Any())
|
||||
{
|
||||
var r = AddDependencyPaths(additionalDependencyPaths);
|
||||
if (!r.IsFailed)
|
||||
{
|
||||
// we have errors, loading may not work.
|
||||
return FluentResults.Result.Fail(new Error($"Failed to load dependency paths")
|
||||
.WithMetadata(MetadataType.ExceptionObject, this)
|
||||
.WithMetadata(MetadataType.RootObject, assemblyFilePath))
|
||||
.WithErrors(r.Errors);
|
||||
}
|
||||
}
|
||||
|
||||
string sanitizedFilePath = Path.GetFullPath(assemblyFilePath.CleanUpPath());
|
||||
string directoryKey = Path.GetDirectoryName(sanitizedFilePath);
|
||||
|
||||
if (directoryKey is null)
|
||||
{
|
||||
return FluentResults.Result.Fail(new Error($"Unable to load assembly: bath file path: {assemblyFilePath}")
|
||||
.WithMetadata(MetadataType.ExceptionObject, this)
|
||||
.WithMetadata(MetadataType.RootObject, sanitizedFilePath));
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var assembly = LoadFromAssemblyPath(sanitizedFilePath);
|
||||
_loadedAssemblyData[assembly] = new AssemblyData(assembly, sanitizedFilePath);
|
||||
return new Result<Assembly>().WithSuccess($"Loaded assembly'{assembly.GetName()}'").WithValue(assembly);
|
||||
}
|
||||
catch (ArgumentNullException ane)
|
||||
{
|
||||
return FluentResults.Result.Fail<Assembly>(new ExceptionalError(ane)
|
||||
.WithMetadata(MetadataType.ExceptionObject, this)
|
||||
.WithMetadata(MetadataType.RootObject, assemblyFilePath)
|
||||
.WithMetadata(MetadataType.ExceptionDetails, ane.Message)
|
||||
.WithMetadata(MetadataType.StackTrace, ane.StackTrace));
|
||||
}
|
||||
catch (ArgumentException ae)
|
||||
{
|
||||
return FluentResults.Result.Fail<Assembly>(new ExceptionalError(ae)
|
||||
.WithMetadata(MetadataType.ExceptionObject, this)
|
||||
.WithMetadata(MetadataType.RootObject, assemblyFilePath)
|
||||
.WithMetadata(MetadataType.ExceptionDetails, ae.Message)
|
||||
.WithMetadata(MetadataType.StackTrace, ae.StackTrace));
|
||||
}
|
||||
catch (FileLoadException fle)
|
||||
{
|
||||
return FluentResults.Result.Fail<Assembly>(new ExceptionalError(fle)
|
||||
.WithMetadata(MetadataType.ExceptionObject, this)
|
||||
.WithMetadata(MetadataType.RootObject, assemblyFilePath)
|
||||
.WithMetadata(MetadataType.ExceptionDetails, fle.Message)
|
||||
.WithMetadata(MetadataType.StackTrace, fle.StackTrace));
|
||||
}
|
||||
catch (FileNotFoundException fnfe)
|
||||
{
|
||||
return FluentResults.Result.Fail<Assembly>(new ExceptionalError(fnfe)
|
||||
.WithMetadata(MetadataType.ExceptionObject, this)
|
||||
.WithMetadata(MetadataType.RootObject, assemblyFilePath)
|
||||
.WithMetadata(MetadataType.ExceptionDetails, fnfe.Message)
|
||||
.WithMetadata(MetadataType.StackTrace, fnfe.StackTrace));
|
||||
}
|
||||
catch (BadImageFormatException bife)
|
||||
{
|
||||
return FluentResults.Result.Fail<Assembly>(new ExceptionalError(bife)
|
||||
.WithMetadata(MetadataType.ExceptionObject, this)
|
||||
.WithMetadata(MetadataType.RootObject, assemblyFilePath)
|
||||
.WithMetadata(MetadataType.ExceptionDetails, bife.Message)
|
||||
.WithMetadata(MetadataType.StackTrace, bife.StackTrace));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return FluentResults.Result.Fail<Assembly>(new ExceptionalError(e)
|
||||
.WithMetadata(MetadataType.ExceptionObject, this)
|
||||
.WithMetadata(MetadataType.RootObject, assemblyFilePath)
|
||||
.WithMetadata(MetadataType.ExceptionDetails, e.Message)
|
||||
.WithMetadata(MetadataType.StackTrace, e.StackTrace));
|
||||
}
|
||||
}
|
||||
|
||||
public FluentResults.Result<Assembly> GetAssemblyByName(string assemblyName)
|
||||
{
|
||||
if (assemblyName.IsNullOrWhiteSpace())
|
||||
{
|
||||
return FluentResults.Result.Fail(new Error($"Assembly name is null")
|
||||
.WithMetadata(MetadataType.ExceptionObject, this));
|
||||
}
|
||||
|
||||
if (_loadedAssemblyData.TryGetValue(assemblyName, out var data))
|
||||
{
|
||||
return new FluentResults.Result<Assembly>().WithSuccess(new Success($"Assembly found")).WithValue(data.Assembly);
|
||||
}
|
||||
|
||||
foreach (var assembly1 in this.Assemblies.Where(a => !_loadedAssemblyData.ContainsKey(a)))
|
||||
{
|
||||
if (assembly1.GetName().FullName == assemblyName)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!assembly1.Location.IsNullOrWhiteSpace())
|
||||
{
|
||||
_loadedAssemblyData[assembly1] = new AssemblyData(assembly1, assembly1.Location);
|
||||
}
|
||||
// we don't have the original byte array so we can't store it.
|
||||
}
|
||||
catch (NotSupportedException nse) // dynamic assembly or location property threw
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
|
||||
return new FluentResults.Result<Assembly>().WithSuccess(new Success($"Assembly found")).WithValue(assembly1);
|
||||
}
|
||||
}
|
||||
|
||||
return FluentResults.Result.Fail(new Error($"Assembly named { assemblyName } not found!"));
|
||||
}
|
||||
|
||||
public FluentResults.Result<ImmutableArray<Type>> GetTypesInAssemblies()
|
||||
{
|
||||
try
|
||||
{
|
||||
return new FluentResults.Result<ImmutableArray<Type>>().WithValue(_loadedAssemblyData.SelectMany(kvp=> kvp.Value.Types).ToImmutableArray());
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return FluentResults.Result.Fail(new ExceptionalError(e));
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Internals
|
||||
|
||||
protected override Assembly Load(AssemblyName assemblyName)
|
||||
{
|
||||
if (_isResolving.Value)
|
||||
return null;
|
||||
|
||||
_isResolving.Value = true;
|
||||
try
|
||||
{
|
||||
if (_loadedAssemblyData.TryGetValue(assemblyName.FullName, out var data))
|
||||
return data.Assembly;
|
||||
var idSpan = new[] { this.Id };
|
||||
if (_assemblyManagementService.GetLoadedAssembly(assemblyName, in idSpan) is { IsSuccess: true } ret)
|
||||
return ret.Value;
|
||||
return null;
|
||||
}
|
||||
catch (ArgumentNullException _)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isResolving.Value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Use the default import resolver since native libraries are niche and not blocking for unloading.
|
||||
// Implement if conflicts become an issue.
|
||||
/*protected override IntPtr LoadUnmanagedDll(string unmanagedDllName)
|
||||
{
|
||||
// Implement NativeLibrary::InternalLoadUnmanagedDll()
|
||||
throw new NotImplementedException();
|
||||
}*/
|
||||
|
||||
private void OnUnload(AssemblyLoadContext context)
|
||||
{
|
||||
base.Unloading -= OnUnload;
|
||||
var wf = new WeakReference<IAssemblyLoaderService>(this);
|
||||
_eventService.PublishEvent<IEventAssemblyContextUnloading>((sub) => sub.OnAssemblyUnloading(wf));
|
||||
_onUnload?.Invoke(this);
|
||||
this.Dispose(true);
|
||||
}
|
||||
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (ModUtils.Threading.CheckClearAndSetBool(ref _isDisposed))
|
||||
{
|
||||
_operationsLock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
_loadedAssemblyData.Clear();
|
||||
|
||||
}
|
||||
finally
|
||||
{
|
||||
_operationsLock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private readonly record struct AssemblyData
|
||||
{
|
||||
public readonly Assembly Assembly;
|
||||
public readonly OneOf<byte[], string> AssemblyImageOrPath;
|
||||
public readonly MetadataReference AssemblyReference;
|
||||
public readonly ImmutableArray<Type> Types;
|
||||
|
||||
public AssemblyData(Assembly assembly, byte[] assemblyImage)
|
||||
{
|
||||
Assembly = assembly ?? throw new ArgumentNullException(nameof(assembly));
|
||||
AssemblyImageOrPath = assemblyImage ?? throw new ArgumentNullException(nameof(assemblyImage));
|
||||
AssemblyReference = MetadataReference.CreateFromImage(assemblyImage);
|
||||
Types = assembly.GetSafeTypes().ToImmutableArray();
|
||||
}
|
||||
|
||||
public AssemblyData(Assembly assembly, string path)
|
||||
{
|
||||
Assembly = assembly ?? throw new ArgumentNullException(nameof(assembly));
|
||||
AssemblyImageOrPath = path ?? throw new ArgumentNullException(nameof(path));
|
||||
AssemblyReference = MetadataReference.CreateFromFile(path);
|
||||
Types = assembly.GetSafeTypes().ToImmutableArray();
|
||||
}
|
||||
}
|
||||
|
||||
private readonly record struct AssemblyOrStringKey : IEquatable<AssemblyOrStringKey>, IEqualityComparer<AssemblyOrStringKey>
|
||||
{
|
||||
public Assembly Assembly { get; init; }
|
||||
public string AssemblyName { get; init; }
|
||||
public readonly int HashCode;
|
||||
|
||||
public AssemblyOrStringKey(Assembly assembly)
|
||||
{
|
||||
if(assembly == null)
|
||||
throw new ArgumentNullException(nameof(assembly));
|
||||
Assembly = assembly;
|
||||
AssemblyName = assembly.GetName().FullName;
|
||||
if (AssemblyName == null)
|
||||
throw new ArgumentNullException(nameof(AssemblyName));
|
||||
HashCode = AssemblyName.GetHashCode();
|
||||
}
|
||||
|
||||
public AssemblyOrStringKey(string assemblyName)
|
||||
{
|
||||
if (assemblyName.IsNullOrWhiteSpace())
|
||||
throw new ArgumentNullException(nameof(assemblyName));
|
||||
Assembly = null;
|
||||
AssemblyName = assemblyName;
|
||||
HashCode = AssemblyName.GetHashCode();
|
||||
}
|
||||
|
||||
public bool Equals(AssemblyOrStringKey x, AssemblyOrStringKey y)
|
||||
{
|
||||
if (x.Assembly is not null && y.Assembly is not null)
|
||||
return x.Assembly == y.Assembly;
|
||||
return x.AssemblyName == y.AssemblyName;
|
||||
}
|
||||
|
||||
public int GetHashCode(AssemblyOrStringKey obj)
|
||||
{
|
||||
return obj.HashCode;
|
||||
}
|
||||
|
||||
public static implicit operator AssemblyOrStringKey(Assembly assembly) => new AssemblyOrStringKey(assembly);
|
||||
public static implicit operator AssemblyOrStringKey(string name) => new AssemblyOrStringKey(name);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -8,6 +8,7 @@ using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using Barotrauma.LuaCs;
|
||||
using Barotrauma.LuaCs.Services;
|
||||
using Barotrauma.Steam;
|
||||
using Microsoft.CodeAnalysis;
|
||||
@@ -630,8 +631,9 @@ public sealed class CsPackageManager : IDisposable
|
||||
|
||||
bool ShouldRunPackage(ContentPackage package, RunConfig config)
|
||||
{
|
||||
return (!_luaCsSetup.Config.TreatForcedModsAsNormal && config.IsForced())
|
||||
|| (ContentPackageManager.EnabledPackages.All.Contains(package) && config.IsForcedOrStandard());
|
||||
throw new NotImplementedException();
|
||||
/*return (!_luaCsSetup.Config.TreatForcedModsAsNormal && config.IsForced())
|
||||
|| (ContentPackageManager.EnabledPackages.All.Contains(package) && config.IsForcedOrStandard());*/
|
||||
}
|
||||
|
||||
void UpdatePackagesToDisable(ref HashSet<ContentPackage> set,
|
||||
@@ -0,0 +1,106 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Barotrauma.LuaCs.Services;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
|
||||
namespace Barotrauma.LuaCs;
|
||||
|
||||
public interface IAssemblyLoaderService : IService
|
||||
{
|
||||
/// <summary>
|
||||
/// Assembly loader factory for DI registration.
|
||||
/// </summary>
|
||||
/// <param name="assemblyManagementService">The assembly hosting management service.</param>
|
||||
/// <param name="eventService">The event service for publishing.</param>
|
||||
/// <param name="id">The referencing ID. Intended to be used to distinguish between instances.</param>
|
||||
/// <param name="name">The name of the friendly name instance, used for error messages.</param>
|
||||
/// <param name="isReferenceOnlyMode">Loaded assemblies are not intended for execution, just MetadataReferences.</param>
|
||||
delegate IAssemblyLoaderService AssemblyLoaderDelegate(
|
||||
IAssemblyManagementService assemblyManagementService,
|
||||
IEventService eventService, Guid id, string name,
|
||||
bool isReferenceOnlyMode, Action<AssemblyLoader> onUnload);
|
||||
|
||||
/// <summary>
|
||||
/// ID for this instance.
|
||||
/// </summary>
|
||||
Guid Id { get; }
|
||||
/// <summary>
|
||||
/// Indicates that the assemblies in this load context are metadata references only and not
|
||||
/// intended for execution.
|
||||
/// </summary>
|
||||
bool IsReferenceOnlyMode { get; }
|
||||
/// <summary>
|
||||
/// Runtime value of constant <see cref="InternalsAwareAssemblyName"/> for extensibility use.
|
||||
/// </summary>
|
||||
public static readonly string InternalsAccessAssemblyName = InternalsAwareAssemblyName;
|
||||
/// <summary>
|
||||
/// Name for all runtime-compiled assemblies requiring access to <c>internal</c> assembly components. <seealso cref="InternalsVisibleToAttribute"/>
|
||||
/// </summary>
|
||||
public const string InternalsAwareAssemblyName = "InternalsAwareAssembly";
|
||||
|
||||
/// <summary>
|
||||
/// Add additional locations for dependency resolution to use.
|
||||
/// </summary>
|
||||
/// <param name="paths"></param>
|
||||
/// <returns></returns>
|
||||
public FluentResults.Result AddDependencyPaths(ImmutableArray<string> paths);
|
||||
|
||||
/// <summary>
|
||||
/// Compiles the supplied syntaxtrees and options into an in-memory assembly image.
|
||||
/// Builds metadata from loaded assemblies, only supply your own if you have in-memory images not managed by the
|
||||
/// AssemblyManager class.
|
||||
/// </summary>
|
||||
/// <param name="assemblyName"><c>[NotNull]</c>Name reference of the assembly.
|
||||
/// <para><b>[IMPORTANT]</b> This is used to reference this assembly as the true name will be forced if
|
||||
/// publicized assemblies are not used (InternalsVisibleTo Attrib).</para>
|
||||
/// Must be supplied for in-memory assemblies.
|
||||
/// <para>Must be unique to all other assemblies explicitly loaded using this context.</para></param>
|
||||
/// <param name="compileWithInternalAccess">Forces the assembly name to <see cref="InternalsAccessAssemblyName"/> and grants access to <c>internal</c>.</param>
|
||||
/// <para><b>[IMPORTANT]</b>Cannot be null or empty if <see cref="compileWithInternalAccess"/> is false.</para></param>
|
||||
/// <param name="syntaxTrees"><c>[NotNull]</c>Syntax trees to compile into the assembly.</param>
|
||||
/// <param name="metadataReferences">All <c>MetadataReference<c/>s to be used for compilation.
|
||||
/// [IMPORTANT] This method builds metadata from loaded assemblies, only supply your own if you have in-memory
|
||||
/// images not managed by the AssemblyManager class.</param>
|
||||
/// <param name="compilationOptions"><c>[NotNull]</c>CSharp compilation options. This method automatically adds the 'IgnoreAccessChecks' property for compilation.</param>
|
||||
/// <returns>Success state of the operation.</returns>
|
||||
public FluentResults.Result<Assembly> CompileScriptAssembly(
|
||||
[NotNull] string assemblyName,
|
||||
bool compileWithInternalAccess,
|
||||
ImmutableArray<SyntaxTree> syntaxTrees,
|
||||
ImmutableArray<MetadataReference> metadataReferences,
|
||||
CSharpCompilationOptions compilationOptions = null);
|
||||
|
||||
/// <summary>
|
||||
/// Loads the assembly from the provided location and registers all new paths provided with dependency resolution.
|
||||
/// </summary>
|
||||
/// <param name="assemblyFilePath">Absolute path to the managed assembly.</param>
|
||||
/// <param name="additionalDependencyPaths">Additional paths for dependency resolution.</param>
|
||||
/// <returns>Success and reference to the assembly if successful.</returns>
|
||||
public FluentResults.Result<Assembly> LoadAssemblyFromFile(string assemblyFilePath,
|
||||
ImmutableArray<string> additionalDependencyPaths);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the already loaded assembly with the same name.
|
||||
/// </summary>
|
||||
/// <param name="assemblyName">Name of the assembly.</param>
|
||||
/// <returns>Operation success on assembly found and assembly.</returns>
|
||||
public FluentResults.Result<Assembly> GetAssemblyByName(string assemblyName);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of <c>Type</c>s from loaded assemblies.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public FluentResults.Result<ImmutableArray<Type>> GetTypesInAssemblies();
|
||||
|
||||
/// <summary>
|
||||
/// List of loaded assemblies.
|
||||
/// </summary>
|
||||
public IEnumerable<Assembly> Assemblies { get; }
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
using System;
|
||||
using Barotrauma.LuaCs.Events;
|
||||
|
||||
namespace Barotrauma;
|
||||
|
||||
public interface IAssemblyPlugin : IDisposable, IEventPluginPreInitialize, IEventPluginInitialize, IEventPluginLoadCompleted { }
|
||||
@@ -14,7 +14,7 @@ using Microsoft.CodeAnalysis.CSharp;
|
||||
|
||||
[assembly: InternalsVisibleTo("CompiledAssembly")]
|
||||
|
||||
namespace Barotrauma.LuaCs;
|
||||
namespace Barotrauma.LuaCs.Services;
|
||||
|
||||
/// <summary>
|
||||
/// AssemblyLoadContext to compile from syntax trees in memory and to load from disk/file. Provides dependency resolution.
|
||||
Reference in New Issue
Block a user