Skip to content

Commit be2597a

Browse files
committed
feat(deliveries): expose loading dock state
1 parent ee7e2d0 commit be2597a

8 files changed

Lines changed: 664 additions & 3 deletions

File tree

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
using System.Reflection;
2+
using S1API.Deliveries;
3+
using S1API.Items;
4+
using S1API.Property;
5+
using S1API.Vehicles;
6+
7+
#if IL2CPPMELON
8+
using S1Delivery = Il2CppScheduleOne.Delivery;
9+
#elif MONOMELON
10+
using S1Delivery = ScheduleOne.Delivery;
11+
#endif
12+
13+
namespace S1API.Tests.Deliveries;
14+
15+
public sealed class LoadingDockApiTests
16+
{
17+
[Fact]
18+
public void NativeTransitionPatchPointsExistInTargetRuntime()
19+
{
20+
const BindingFlags flags =
21+
BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public;
22+
23+
Assert.NotNull(typeof(S1Delivery.LoadingDock).GetMethod(
24+
"SetOccupant",
25+
flags));
26+
Assert.NotNull(typeof(S1Delivery.LoadingDock).GetMethod(
27+
nameof(S1Delivery.LoadingDock.SetStaticOccupant),
28+
flags));
29+
Assert.NotNull(typeof(S1Delivery.LoadingDock).GetMethod(
30+
"set_IsAcceptingItems",
31+
flags));
32+
}
33+
34+
[Fact]
35+
public void WrapperSurfaceIsReadOnlyAndManaged()
36+
{
37+
Assert.Empty(typeof(LoadingDock).GetConstructors(
38+
BindingFlags.Public | BindingFlags.Instance));
39+
Assert.All(
40+
typeof(LoadingDock).GetProperties(BindingFlags.Public | BindingFlags.Instance),
41+
property => Assert.Null(property.SetMethod));
42+
43+
Assert.Equal(typeof(string), GetProperty(nameof(LoadingDock.GUID)).PropertyType);
44+
Assert.Equal(typeof(string), GetProperty(nameof(LoadingDock.Name)).PropertyType);
45+
Assert.Equal(typeof(PropertyWrapper), GetProperty(nameof(LoadingDock.Property)).PropertyType);
46+
Assert.Equal(
47+
typeof(IReadOnlyList<ItemSlotInstance>),
48+
GetProperty(nameof(LoadingDock.InputSlots)).PropertyType);
49+
Assert.Equal(
50+
typeof(IReadOnlyList<ItemSlotInstance>),
51+
GetProperty(nameof(LoadingDock.OutputSlots)).PropertyType);
52+
Assert.Equal(typeof(LandVehicle), GetProperty(nameof(LoadingDock.DynamicOccupant)).PropertyType);
53+
Assert.Equal(typeof(LandVehicle), GetProperty(nameof(LoadingDock.StaticOccupant)).PropertyType);
54+
55+
Assert.DoesNotContain(
56+
typeof(LoadingDock).GetMembers(BindingFlags.Public | BindingFlags.Instance),
57+
ExposesNativeDeliveryType);
58+
}
59+
60+
[Fact]
61+
public void PropertyAndDeliveryExposeLoadingDockNavigation()
62+
{
63+
Assert.Equal(
64+
typeof(IReadOnlyList<LoadingDock>),
65+
typeof(PropertyWrapper).GetProperty(nameof(PropertyWrapper.LoadingDocks))!.PropertyType);
66+
Assert.Equal(
67+
typeof(LoadingDock),
68+
typeof(Delivery).GetProperty(nameof(Delivery.LoadingDock))!.PropertyType);
69+
}
70+
71+
[Fact]
72+
public void EventsExposeManagedPreviousAndCurrentValues()
73+
{
74+
Assert.Equal(
75+
typeof(Action<LandVehicle?, LandVehicle?>),
76+
GetEvent(nameof(LoadingDock.DynamicOccupantChanged)).EventHandlerType);
77+
Assert.Equal(
78+
typeof(Action<LandVehicle?, LandVehicle?>),
79+
GetEvent(nameof(LoadingDock.StaticOccupantChanged)).EventHandlerType);
80+
Assert.Equal(
81+
typeof(Action<bool, bool>),
82+
GetEvent(nameof(LoadingDock.AcceptingItemsChanged)).EventHandlerType);
83+
}
84+
85+
[Fact]
86+
public void ManagedNotificationsSuppressNoOpsAndIsolateSubscribers()
87+
{
88+
var nativeDock = TestObjectFactory.CreateUninitialized<S1Delivery.LoadingDock>();
89+
var dock = new LoadingDock(nativeDock);
90+
var previous = TestObjectFactory.CreateUninitialized<LandVehicle>();
91+
var current = TestObjectFactory.CreateUninitialized<LandVehicle>();
92+
int dynamicCalls = 0;
93+
int staticCalls = 0;
94+
int acceptingCalls = 0;
95+
96+
dock.DynamicOccupantChanged += (_, _) => throw new InvalidOperationException("expected");
97+
dock.DynamicOccupantChanged += (observedPrevious, observedCurrent) =>
98+
{
99+
Assert.Same(previous, observedPrevious);
100+
Assert.Same(current, observedCurrent);
101+
dynamicCalls++;
102+
};
103+
dock.StaticOccupantChanged += (_, _) => staticCalls++;
104+
dock.AcceptingItemsChanged += (observedPrevious, observedCurrent) =>
105+
{
106+
Assert.False(observedPrevious);
107+
Assert.True(observedCurrent);
108+
acceptingCalls++;
109+
};
110+
111+
dock.NotifyDynamicOccupantChanged(previous, previous);
112+
dock.NotifyDynamicOccupantChanged(previous, current);
113+
dock.NotifyStaticOccupantChanged(current, current);
114+
dock.NotifyStaticOccupantChanged(previous, current);
115+
dock.NotifyAcceptingItemsChanged(false, false);
116+
dock.NotifyAcceptingItemsChanged(false, true);
117+
118+
Assert.Equal(1, dynamicCalls);
119+
Assert.Equal(1, staticCalls);
120+
Assert.Equal(1, acceptingCalls);
121+
}
122+
123+
private static PropertyInfo GetProperty(string name) =>
124+
typeof(LoadingDock).GetProperty(name, BindingFlags.Public | BindingFlags.Instance)!;
125+
126+
private static EventInfo GetEvent(string name) =>
127+
typeof(LoadingDock).GetEvent(name, BindingFlags.Public | BindingFlags.Instance)!;
128+
129+
private static bool ExposesNativeDeliveryType(MemberInfo member)
130+
{
131+
IEnumerable<Type> types = member switch
132+
{
133+
PropertyInfo property => new[] { property.PropertyType },
134+
EventInfo eventInfo when eventInfo.EventHandlerType != null =>
135+
new[] { eventInfo.EventHandlerType },
136+
MethodInfo method => new[] { method.ReturnType }
137+
.Concat(method.GetParameters().Select(parameter => parameter.ParameterType)),
138+
_ => Array.Empty<Type>()
139+
};
140+
141+
return types
142+
.SelectMany(ExpandType)
143+
.Any(type => type.Namespace?.Contains(
144+
"ScheduleOne.Delivery",
145+
StringComparison.Ordinal) == true);
146+
}
147+
148+
private static IEnumerable<Type> ExpandType(Type root)
149+
{
150+
yield return root;
151+
152+
if (root.HasElementType && root.GetElementType() is Type elementType)
153+
{
154+
foreach (Type nested in ExpandType(elementType))
155+
yield return nested;
156+
}
157+
158+
foreach (Type argument in root.GetGenericArguments())
159+
{
160+
foreach (Type nested in ExpandType(argument))
161+
yield return nested;
162+
}
163+
}
164+
}
165+
166+
internal static class LoadingDockApiCompileFixture
167+
{
168+
internal static void Observe(
169+
PropertyWrapper property,
170+
Delivery delivery,
171+
LoadingDock dock)
172+
{
173+
IReadOnlyList<LoadingDock> propertyDocks = property.LoadingDocks;
174+
LoadingDock? selectedDock = delivery.LoadingDock;
175+
IReadOnlyList<ItemSlotInstance> inputSlots = dock.InputSlots;
176+
IReadOnlyList<ItemSlotInstance> outputSlots = dock.OutputSlots;
177+
LandVehicle? dynamicOccupant = dock.DynamicOccupant;
178+
LandVehicle? staticOccupant = dock.StaticOccupant;
179+
180+
Action<LandVehicle?, LandVehicle?> vehicleHandler = (_, _) => { };
181+
Action<bool, bool> acceptingHandler = (_, _) => { };
182+
dock.DynamicOccupantChanged += vehicleHandler;
183+
dock.StaticOccupantChanged += vehicleHandler;
184+
dock.AcceptingItemsChanged += acceptingHandler;
185+
dock.DynamicOccupantChanged -= vehicleHandler;
186+
dock.StaticOccupantChanged -= vehicleHandler;
187+
dock.AcceptingItemsChanged -= acceptingHandler;
188+
189+
_ = propertyDocks;
190+
_ = selectedDock;
191+
_ = inputSlots;
192+
_ = outputSlots;
193+
_ = dynamicOccupant;
194+
_ = staticOccupant;
195+
}
196+
}

S1API/Deliveries/Delivery.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,27 @@ internal Delivery(S1Delivery.DeliveryInstance delivery)
5151
/// </summary>
5252
public int LoadingDockIndex => NativeDelivery.LoadingDockIndex;
5353

54+
/// <summary>
55+
/// Gets the selected destination loading dock, or <see langword="null"/> while it is unavailable.
56+
/// </summary>
57+
public LoadingDock? LoadingDock
58+
{
59+
get
60+
{
61+
try
62+
{
63+
var loadingDock = NativeDelivery.LoadingDock;
64+
return loadingDock == null
65+
? null
66+
: global::S1API.Deliveries.LoadingDock.Wrap(loadingDock);
67+
}
68+
catch
69+
{
70+
return null;
71+
}
72+
}
73+
}
74+
5475
/// <summary>
5576
/// Gets the wrapped destination property, or <see langword="null"/> while it is unavailable.
5677
/// </summary>

0 commit comments

Comments
 (0)