Skip to content

Commit fe312a0

Browse files
committed
Extract non-ML specific helpers into their own package and included in the Game Pack
1 parent 69316de commit fe312a0

25 files changed

Lines changed: 752 additions & 525 deletions

Directory.Build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<Project>
22
<PropertyGroup>
3-
<Version>0.29.0</Version>
3+
<Version>0.30.0</Version>
44
<RootNamespace>MonkeyLoader.Resonite</RootNamespace>
55
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
66

MonkeyLoader.GamePacks.Resonite.sln

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{
3232
.github\workflows\publish.yml = .github\workflows\publish.yml
3333
EndProjectSection
3434
EndProject
35+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MonkeyLoader.Resonite.Core", "MonkeyLoader.Resonite.Core\MonkeyLoader.Resonite.Core.csproj", "{1DD99E73-5628-4400-84B3-D175B03A99C5}"
36+
EndProject
3537
Global
3638
GlobalSection(SolutionConfigurationPlatforms) = preSolution
3739
Debug|Any CPU = Debug|Any CPU
@@ -54,6 +56,10 @@ Global
5456
{E5612DD7-954D-43A8-A0F0-F51E3144A994}.Debug|Any CPU.Build.0 = Debug|Any CPU
5557
{E5612DD7-954D-43A8-A0F0-F51E3144A994}.Release|Any CPU.ActiveCfg = Release|Any CPU
5658
{E5612DD7-954D-43A8-A0F0-F51E3144A994}.Release|Any CPU.Build.0 = Release|Any CPU
59+
{1DD99E73-5628-4400-84B3-D175B03A99C5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
60+
{1DD99E73-5628-4400-84B3-D175B03A99C5}.Debug|Any CPU.Build.0 = Debug|Any CPU
61+
{1DD99E73-5628-4400-84B3-D175B03A99C5}.Release|Any CPU.ActiveCfg = Release|Any CPU
62+
{1DD99E73-5628-4400-84B3-D175B03A99C5}.Release|Any CPU.Build.0 = Release|Any CPU
5763
EndGlobalSection
5864
GlobalSection(SolutionProperties) = preSolution
5965
HideSolutionNode = FALSE

MonkeyLoader.Resonite.Integration/DestroyOnUserLeaveExtensions.cs renamed to MonkeyLoader.Resonite.Core/DestroyOnUserLeaveExtensions.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
using FrooxEngine;
22
using System.Linq;
3+
using System.Runtime.CompilerServices;
34

45
namespace MonkeyLoader.Resonite
56
{
67
/// <summary>
78
/// Contains an extension method to add <see cref="DestroyOnUserLeave"/> components targeting
89
/// the <see cref="World.LocalUser">local user</see> to mod additions that rely on the local user being there to work.
910
/// </summary>
11+
[TypeForwardedFrom("MonkeyLoader.Resonite.Integration")]
1012
public static class DestroyOnUserLeaveExtensions
1113
{
1214
/// <summary>

MonkeyLoader.Resonite.Integration/DynamicVariableExtensions.cs renamed to MonkeyLoader.Resonite.Core/DynamicVariableExtensions.cs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.Diagnostics.CodeAnalysis;
77
using System.IO;
88
using System.Linq;
9+
using System.Runtime.CompilerServices;
910
using System.Text;
1011
using System.Threading.Tasks;
1112

@@ -15,6 +16,7 @@ namespace MonkeyLoader.Resonite
1516
/// Contains extension methods related to <see cref="DynamicVariableSpace"/>s
1617
/// and their <see cref="IDynamicVariable">variables</see>.
1718
/// </summary>
19+
[TypeForwardedFrom("MonkeyLoader.Resonite.Integration")]
1820
public static class DynamicVariableExtensions
1921
{
2022
//public static DynamicReference<T> CreateReferenceVariable<T>(this SyncRef<T> syncRef, string name, bool overrideOnLink = false, bool persistent = true)
@@ -121,7 +123,7 @@ public static IEnumerable<DynamicVariableSpace> GetAvailableSpaces(this Slot slo
121123
/// <returns>All full Dynamic Variable identities that apply to this <see cref="Slot"/> that the given <see cref="Type"/> can be assigned to.</returns>
122124
/// <inheritdoc cref="GetAvailableVariableIdentities(Slot)"/>
123125
public static IEnumerable<DynamicVariableIdentity> GetAvailableVariableIdentities(this Slot slot, Type type)
124-
=> slot.GetAvailableSpaces().SelectMany(space => space.GetVariableIdentities(type)).ToArray();
126+
=> [.. slot.GetAvailableSpaces().SelectMany(space => space.GetVariableIdentities(type))];
125127

126128
/// <summary>
127129
/// Gets all <see cref="DynamicVariableIdentity">full Dynamic Variable
@@ -133,7 +135,7 @@ public static IEnumerable<DynamicVariableIdentity> GetAvailableVariableIdentitie
133135
/// <returns>All full Dynamic Variable identities that apply to this <see cref="Slot"/> that have the given <paramref name="name"/>.</returns>
134136
/// <inheritdoc cref="GetAvailableVariableIdentities(Slot)"/>
135137
public static IEnumerable<DynamicVariableIdentity> GetAvailableVariableIdentities(this Slot slot, string name)
136-
=> slot.GetAvailableSpaces().SelectMany(space => space.GetVariableIdentities(name)).ToArray();
138+
=> [.. slot.GetAvailableSpaces().SelectMany(space => space.GetVariableIdentities(name))];
137139

138140
/// <summary>
139141
/// Gets all <see cref="DynamicVariableIdentity">full Dynamic Variable
@@ -144,7 +146,7 @@ public static IEnumerable<DynamicVariableIdentity> GetAvailableVariableIdentitie
144146
/// <returns>All full Dynamic Variable identities that apply to this <see cref="Slot"/> that <typeparamref name="T"/> can be assigned to.</returns>
145147
/// <inheritdoc cref="GetAvailableVariableIdentities(Slot)"/>
146148
public static IEnumerable<DynamicVariableIdentity> GetAvailableVariableIdentities<T>(this Slot slot)
147-
=> slot.GetAvailableSpaces().SelectMany(GetVariableIdentities<T>).ToArray();
149+
=> [.. slot.GetAvailableSpaces().SelectMany(GetVariableIdentities<T>)];
148150

149151
/// <summary>
150152
/// Gets all <see cref="DynamicVariableIdentity">full Dynamic Variable
@@ -161,7 +163,7 @@ public static IEnumerable<DynamicVariableIdentity> GetAvailableVariableIdentitie
161163
/// <param name="slot">The <see cref="Slot"/> to find all applicable full Dynamic Variable identities for.</param>
162164
/// <returns>All full Dynamic Variable identities that apply to this <see cref="Slot"/>.</returns>
163165
public static IEnumerable<DynamicVariableIdentity> GetAvailableVariableIdentities(this Slot slot)
164-
=> slot.GetAvailableSpaces().SelectMany(GetVariableIdentities).ToArray();
166+
=> [.. slot.GetAvailableSpaces().SelectMany(GetVariableIdentities)];
165167

166168
/// <summary>
167169
/// Gets the <see cref="DynamicVariableHandler{T}"/> of this
@@ -337,7 +339,7 @@ public static IEnumerable<DynamicVariableIdentity> GetVariableIdentities<T>(this
337339
/// <param name="type">The type that must be assignable to the variables.</param>
338340
/// <returns>All full Dynamic Variable identities associated with this <paramref name="space"/> that the given <see cref="Type"/> can be assigned to.</returns>
339341
public static IEnumerable<DynamicVariableIdentity> GetVariableIdentities(this DynamicVariableSpace space, Type type)
340-
=> space.GetVariableIdentities().Where(id => id.Type.IsAssignableFrom(type)).ToArray();
342+
=> [.. space.GetVariableIdentities().Where(id => id.Type.IsAssignableFrom(type))];
341343

342344
/// <summary>
343345
/// Gets all <see cref="DynamicVariableIdentity">full Dynamic Variable identities</see>
@@ -348,7 +350,7 @@ public static IEnumerable<DynamicVariableIdentity> GetVariableIdentities(this Dy
348350
/// <param name="name">The name that the variable identities must have.</param>
349351
/// <returns>All full Dynamic Variable identities associated with this <paramref name="space"/> that have the given <paramref name="name"/>.</returns>
350352
public static IEnumerable<DynamicVariableIdentity> GetVariableIdentities(this DynamicVariableSpace space, string name)
351-
=> space.GetVariableIdentities().Where(id => id.Name == name).ToArray();
353+
=> [.. space.GetVariableIdentities().Where(id => id.Name == name)];
352354

353355
//public static DynamicField<T>? CreateVariable<T>(this IField<T> field, string name, bool overrideOnLink = false, bool persistent = true)
354356
//{

MonkeyLoader.Resonite.Integration/DynamicVariableIdentity.cs renamed to MonkeyLoader.Resonite.Core/DynamicVariableIdentity.cs

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
using FrooxEngine;
22
using System;
33
using System.Collections.Generic;
4+
using System.Linq;
5+
using System.Runtime.CompilerServices;
46
using System.Text;
57

68
namespace MonkeyLoader.Resonite
79
{
810
/// <summary>
9-
/// Fully describes the identity of a Dynamic Variable based on itsn <see cref="Type">Type</see>,
11+
/// Fully describes the identity of a Dynamic Variable based on its <see cref="Type">Type</see>,
1012
/// <see cref="Name">Name</see>, and the <see cref="Space">Space</see> it's a part of.
1113
/// </summary>
14+
[TypeForwardedFrom("MonkeyLoader.Resonite.Integration")]
1215
public readonly struct DynamicVariableIdentity : IEquatable<DynamicVariableIdentity>
1316
{
1417
/// <summary>
@@ -83,6 +86,22 @@ public override readonly int GetHashCode()
8386

8487
/// <inheritdoc/>
8588
public override readonly string ToString()
86-
=> $"Dynamic Variable {Name} of Type {Type.CompactDescription()} on {Space.GetReferenceLabel()}";
89+
=> $"Dynamic Variable {Name} of Type {CompactDescription(Type)} on {Space.GetReferenceLabel()}";
90+
91+
/// <summary>
92+
/// Gets a compact, human-readable description of a type.
93+
/// </summary>
94+
/// <param name="type">The type to format.</param>
95+
/// <returns>The human-readable description of the type.</returns>
96+
private static string CompactDescription(Type type)
97+
{
98+
if (type is null)
99+
return "null";
100+
101+
if (type.IsGenericType)
102+
return $"{type.Name}<{string.Join(", ", type.GetGenericArguments().Select(CompactDescription))}>";
103+
104+
return type.Name;
105+
}
87106
}
88107
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
using FrooxEngine;
2+
using System;
3+
using System.Collections.Generic;
4+
using System.Runtime.CompilerServices;
5+
using System.Text;
6+
7+
namespace MonkeyLoader.Resonite
8+
{
9+
/// <summary>
10+
/// Contains extension methods for <see cref="IField{T}">fields</see>
11+
/// and other <see cref="IWorldElement">world elements</see>
12+
/// </summary>
13+
[TypeForwardedFrom("MonkeyLoader.Resonite.Integration")]
14+
public static class FieldExtensions
15+
{
16+
/// <summary>
17+
/// Creates a label describing the <paramref name="target"/> reference as a <see cref="RefEditor"/> would.
18+
/// </summary>
19+
/// <param name="target">The reference to label.</param>
20+
/// <returns>A label for the <paramref name="target"/> reference if it is not <c>null</c>; otherwise, <c>&lt;i&gt;null&lt;/i&gt;</c>.</returns>
21+
public static string GetReferenceLabel(this IWorldElement? target)
22+
{
23+
if (target is null)
24+
return "<i>null</i>";
25+
26+
if (target is Slot targetSlot)
27+
return $"{targetSlot.Name} ({target.ReferenceID})";
28+
29+
var component = target.FindNearestParent<Component>();
30+
var slot = component?.Slot ?? target.FindNearestParent<Slot>();
31+
32+
var arg = (component is not null && component != target) ? ("on " + component.Name + " on " + slot.Name) : ((slot is null) ? "" : ("on " + slot.Name));
33+
return (target is not SyncElement syncElement) ? $"{target.Name ?? target.GetType().Name} {arg} ({target.ReferenceID})" : $"{syncElement.NameWithPath} {arg} ({target.ReferenceID})";
34+
}
35+
}
36+
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
using FrooxEngine;
2+
using System;
3+
using System.Diagnostics.CodeAnalysis;
4+
using System.Runtime.CompilerServices;
5+
6+
namespace MonkeyLoader.Resonite
7+
{
8+
/// <summary>
9+
/// Contains extension methods to deal with <see cref="World.Keys">keyed</see>
10+
/// <see cref="Component"/>s in <see cref="World"/>s.
11+
/// </summary>
12+
[TypeForwardedFrom("MonkeyLoader.Resonite.Integration")]
13+
public static class KeyedComponentHelper
14+
{
15+
/// <summary>
16+
/// <see cref="World.KeyOwner">Gets</see> or <see cref="World.RequestKey">creates</see>
17+
/// a keyed <typeparamref name="T"/> component, based on the situation and parameters.
18+
/// </summary>
19+
/// <returns>The existing or newly created <typeparamref name="T"/> component.</returns>
20+
/// <exception cref="InvalidOperationException">
21+
/// When an incompatible component is associated with the given <paramref name="key"/>
22+
/// and <paramref name="replaceExisting"/> was <see langword="false"/>.
23+
/// </exception>
24+
/// <inheritdoc cref="TryGetKeyedComponentOrCreate{T}"/>
25+
public static T GetKeyedComponentOrCreate<T>(this Slot slot, string key, Action<T>? onCreated = null,
26+
int version = 0, bool replaceExisting = false, bool updateExisting = false)
27+
where T : Component, new()
28+
{
29+
if (!slot.TryGetKeyedComponentOrCreate(key, out var component, onCreated, version, replaceExisting, updateExisting))
30+
throw new InvalidOperationException("An incompatible component is stored under the key " + key + $"\nExisting: {component}");
31+
32+
return component;
33+
}
34+
35+
/// <summary>
36+
/// Tries to <see cref="World.KeyOwner">get</see> or <see cref="World.RequestKey">create</see>
37+
/// a keyed <typeparamref name="T"/> component, based on the situation and parameters.
38+
/// </summary>
39+
/// <remarks><para>
40+
/// This behavior was adapted from <see cref="ContainerWorker{C}.GetComponentOrAttach{T}(Predicate{T})"/>.
41+
/// </para><para>
42+
/// Consider that these component-associations will be saved with the <see cref="World"/>.
43+
/// Make sure that this is really necessary for what you're attempting to do.
44+
/// </para></remarks>
45+
/// <typeparam name="T">The type of the component to get or create.</typeparam>
46+
/// <param name="slot">The slot to attach the <typeparamref name="T"/> component to when necessary.</param>
47+
/// <param name="key">The unique key (to) associate(d) with the <typeparamref name="T"/> component.</param>
48+
/// <param name="component">
49+
/// The <typeparamref name="T"/> component associated with the given <paramref name="key"/> if this call returns <see langword="true"/>;
50+
/// otherwise, <see langword="null"/> when an incompatible component is associated with it
51+
/// and <paramref name="replaceExisting"/> was <see langword="false"/>.
52+
/// </param>
53+
/// <param name="onCreated">
54+
/// The optional configuration action to call when the <typeparamref name="T"/> component had to be created,
55+
/// or when its saved version is lower than the given <paramref name="version"/> number.
56+
/// </param>
57+
/// <param name="version">The version number to associate with the <typeparamref name="T"/> component after this call.</param>
58+
/// <param name="replaceExisting">Whether to replace a different component associated with the given <paramref name="key"/> with this one.</param>
59+
/// <param name="updateExisting">
60+
/// Whether to call <paramref name="onCreated"/> even if the <typeparamref name="T"/> component already exists.<br/>
61+
/// If the given <paramref name="version"/> number is greater than zero and the saved version is lower, this will always be done.
62+
/// </param>
63+
/// <returns><see langword="true"/> if the keyed <typeparamref name="T"/> component was found or created; otherwise, <see langword="false"/>.</returns>
64+
public static bool TryGetKeyedComponentOrCreate<T>(this Slot slot, string key, [NotNullWhen(true)] out T? component,
65+
Action<T>? onCreated = null, int version = 0, bool replaceExisting = false, bool updateExisting = false)
66+
where T : Component, new()
67+
{
68+
var keyedComponent = slot.World.KeyOwner(key);
69+
70+
if (keyedComponent is not null)
71+
{
72+
if (keyedComponent is T typedComponent)
73+
{
74+
if (version > 0 && slot.World.KeyVersion(key) < version)
75+
{
76+
updateExisting = true;
77+
slot.World.RequestKey(typedComponent, key, version, false);
78+
}
79+
80+
if (updateExisting)
81+
onCreated?.Invoke(typedComponent);
82+
83+
component = typedComponent;
84+
return true;
85+
}
86+
87+
if (!replaceExisting)
88+
{
89+
component = null;
90+
return false;
91+
}
92+
}
93+
94+
component = slot.AttachComponent<T>();
95+
96+
slot.World.RequestKey(component, key, version, false);
97+
onCreated?.Invoke(component);
98+
99+
return true;
100+
}
101+
}
102+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<Project Sdk="Remora.Resonite.Sdk">
2+
<PropertyGroup>
3+
<ResoniteProjectType>library</ResoniteProjectType>
4+
<ResoniteInstallOnBuild>false</ResoniteInstallOnBuild>
5+
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
6+
</PropertyGroup>
7+
8+
<PropertyGroup>
9+
<PackageId>MonkeyLoader.GamePacks.Resonite.Core</PackageId>
10+
<Title>Resonite Game Pack Core</Title>
11+
<Authors>Banane9</Authors>
12+
<Description>This package provides the non-ML specific helpers for modding the game Resonite, which uses FrooxEngine.
13+
It contains many useful features for the Developers of mods and their Users alike.
14+
15+
This package is only intended for use with non-ML mods.
16+
For mods targetting ML, this library is part of the Resonite Game Pack.</Description>
17+
<PackageTags>mod;mods;modding;mod;loader;monkeyloader;resonite;tools;tool;toolkit;helper;helpers;standalone</PackageTags>
18+
</PropertyGroup>
19+
20+
<ItemGroup>
21+
<None Include="README.md" Pack="true" PackagePath="" />
22+
<None Include="../Icon.png" Pack="true" PackagePath="" />
23+
<None Include="Locale/**/*.json" Pack="true" PackagePath="content/Locale" />
24+
</ItemGroup>
25+
26+
<ItemGroup>
27+
<InternalsVisibleTo Include="MonkeyLoader.Resonite.Integration" />
28+
</ItemGroup>
29+
30+
<ItemGroup>
31+
<PackageReference Include="Lib.Harmony.Thin" Version="2.4.2" />
32+
</ItemGroup>
33+
34+
<ItemGroup>
35+
<ResoniteReference Include="FrooxEngine" UsePublicized="true" />
36+
<ResoniteReference Include="Elements.Core" />
37+
<ResoniteReference Include="Renderite.Shared" />
38+
</ItemGroup>
39+
</Project>
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
Resonite Game Pack Toolkit
2+
==========================
3+
4+
This package provides the non-ML specific helpers for modding the game [Resonite](https://resonite.com/), which uses FrooxEngine.
5+
It contains many useful features for the Developers of mods and their Users alike.
6+
7+
This package is only intended for use with non-ML mods.
8+
For mods targetting ML, this library is part of the Resonite Game Pack.

0 commit comments

Comments
 (0)