-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathResourceManager.cs
More file actions
204 lines (173 loc) · 7.81 KB
/
ResourceManager.cs
File metadata and controls
204 lines (173 loc) · 7.81 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization;
using Newtonsoft.Json.Linq;
using Raylib_cs;
namespace TipeEngine
{
public static class ResourceManager
{
private static readonly Dictionary<string, object> loadedObjects = [];
private static readonly Dictionary<string, ComponentContext> ComponentCache = [];
private static readonly Dictionary<Type, Func<string, object>> CustomDeserializers = new()
{
{ typeof(Texture2D), static path => LoadTexture(path) },
{ typeof(Image), static path => LoadImage(path) },
{ typeof(Sound), static path => LoadSound(path) },
{ typeof(GameObject), static _ => throw new NotSupportedException($"can't deserialize a nested GameObject") }
};
internal static void CacheComponents()
{
foreach (Type type in AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(static a => a.GetTypes())
.Where(static t => typeof(IComponent).IsAssignableFrom(t)))
{
string name = type.FullName ?? throw new UnreachableException("some how.... a type has no name...");
ConstructorInfo[] constructors = type.GetConstructors(BindingFlags.Public | BindingFlags.Instance);
if (constructors.Length == 0)
{
continue;
}
ConstructorInfo constructor =
constructors.FirstOrDefault(static c => Attribute.IsDefined(c, typeof(PreferredConstructorAttribute))) ??
constructors.FirstOrDefault() ??
throw new UnreachableException("There are more than 0 contstructors yet it failed to get the first one");
ParameterInfo[] parameters = constructor.GetParameters();
ComponentContext context = new(type, constructor);
ComponentCache[name] = context;
}
}
public static T LoadGameObject<T>(string path, string variant = "") where T : GameObject
{
return (T)LoadGameObject(path, typeof(T), variant);
}
public static object LoadGameObject(string path, Type type, string variant = "")
{
path += string.IsNullOrEmpty(variant) ? $"{type.Name}.json" : $"{type.Name}_{variant}.json";
string json = File.ReadAllText(path);
JObject root = JObject.Parse(json);
JToken objectToken = root["object"] ??
throw new SerializationException($"Could not find `object` in `{path}`");
object? gameObjectRaw = objectToken.ToObject(type);
if (gameObjectRaw is not GameObject gameObject)
{
throw new SerializationException($"Failed to deserialize `object` from `{path}`");
}
JToken? componentsToken = root["components"];
if (componentsToken == null)
{
return gameObject;
}
if (componentsToken is JArray componentsArray)
{
foreach (JToken componentToken in componentsArray)
{
if (componentToken is not JObject componentObject)
{
continue;
}
IComponent component = LoadComponent(componentObject, gameObject);
_ = gameObject.AddComponent(component);
}
}
return gameObject;
}
private static IComponent LoadComponent(JObject componentObject, GameObject? baseObject = null)
{
string componentTypeName = componentObject["type"]?.ToString() ??
throw new SerializationException("failed to get component type");
if (!ComponentCache.TryGetValue(componentTypeName, out ComponentContext componentContext))
{
throw new SerializationException($"can't find component context of `{componentTypeName}`");
}
ConstructorInfo constructor = componentContext.constructor;
ParameterInfo[] parameters = componentContext.parameters;
List<object> parameterList = [];
foreach (ParameterInfo parameter in parameters)
{
Type parameterType = parameter.ParameterType;
string parameterName = parameter.Name!;
JToken parameterToken = componentObject[parameterName] ??
throw new SerializationException($"no `{parameterName}` on component `{componentTypeName}`");
object parameterObject = parameterType.IsGenericType
? parameterToken.ToObject(parameterType) ??
throw new SerializationException($"failed to deserialize `{parameterName}` from `{componentTypeName}`")
: ResolveParameter(parameterType, parameterToken, baseObject, parameterName, componentTypeName);
parameterList.Add(parameterObject);
}
return (IComponent)constructor.Invoke([.. parameterList]);
}
private static object ResolveParameter(Type parameterType, JToken token, GameObject? baseObject, string paramName, string componentName)
{
string value = token.ToObject<string>() ??
throw new SerializationException($"failed to deserialize `{paramName}`");
return parameterType == typeof(GameObject) && value == "base"
? baseObject ?? throw new ArgumentNullException(nameof(baseObject), "Base object was not provided.")
: CustomDeserializers.TryGetValue(parameterType, out Func<string, object>? loader)
? loader(value)
: throw new SerializationException($"Unhandled type {parameterType} in `{componentName}`");
}
private static Sound LoadSound(string path)
{
return LoadWithCache(path, Raylib.LoadSound);
}
private static Image LoadImage(string path)
{
return LoadWithCache(path, Raylib.LoadImage);
}
private static Texture2D LoadTexture(string path)
{
return LoadWithCache(path, Raylib.LoadTexture);
}
private static T LoadWithCache<T>(string path, Func<string, T> loader)
{
if (loadedObjects.TryGetValue(path, out object? cache) && cache is T t)
{
return t;
}
T result = loader(path);
loadedObjects[path] = result!;
return result;
}
public static void ClearObjects()
{
foreach (object loadedObject in loadedObjects.Values)
{
if (loadedObject is Sound sound)
{
Raylib.UnloadSound(sound);
}
else if (loadedObject is Image image)
{
Raylib.UnloadImage(image);
}
else if (loadedObject is Texture2D texture)
{
Raylib.UnloadTexture(texture);
}
}
loadedObjects.Clear();
}
}
public readonly struct ComponentContext
{
public readonly Type type { get; }
public readonly ConstructorInfo constructor { get; }
public readonly ParameterInfo[] parameters { get; }
public ComponentContext(Type _type, ConstructorInfo _constructor)
{
type = _type;
constructor = _constructor;
parameters = constructor.GetParameters();
}
}
[AttributeUsage(AttributeTargets.Constructor, AllowMultiple = false)]
public class PreferredConstructorAttribute : Attribute
{
public PreferredConstructorAttribute() { }
}
}