Skip to content

Commit 95fa714

Browse files
author
Marco Klein
committed
feat: ✨ Implement asynchronous scene and resource loading
1 parent 7e70816 commit 95fa714

File tree

5 files changed

+125
-7
lines changed

5 files changed

+125
-7
lines changed

Source/Composition/Extensions/IServiceCollectionExtensions.cs

+7
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,13 @@ public static IServiceCollection MapSingleton<TNewMapping, TSource>(
1111
return collection.AddSingleton<TNewMapping>(x => x.GetRequiredService<TSource>());
1212
}
1313

14+
public static IServiceCollection MapSingleton<TNewMapping>(
15+
this IServiceCollection collection, Type type)
16+
where TNewMapping : class {
17+
18+
return collection.AddSingleton<TNewMapping>(x => x.GetRequiredService(type) as TNewMapping);
19+
}
20+
1421
public static IServiceCollection AddFactory<TService, TImplementation>(this IServiceCollection services)
1522
where TService : class
1623
where TImplementation : class, TService {

Source/Composition/Extensions/NodeExtension.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88

99
namespace GoDough.Composition.Extensions {
1010
public static class NodeCompositionExtensions {
11-
public static Node WireNode(this Node node) {
11+
public static T WireNode<T>(this T node)
12+
where T : Node {
1213
try {
1314
Func<Attribute, bool> isInjectionAttribute = x =>
1415
x.GetType().IsAssignableFrom(typeof(InjectAttribute));

Source/Runtime/GodotApi.cs

+74-2
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,89 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Threading.Tasks;
15
using Godot;
6+
using GoDough.Runtime.LivecycleHooks;
27

38
namespace GoDough.Runtime {
49
internal class GodotApi : IGodotApi {
10+
private readonly Dictionary<string, List<Action<double>>> _threadedResourceLoadProgress = new Dictionary<string, List<Action<double>>>();
11+
private readonly Dictionary<string, Task<Resource>> _resourceLoadTasks = new Dictionary<string, Task<Resource>>();
12+
private readonly Timer _timer;
13+
14+
public GodotApi(AppHost appHost) {
15+
this._timer = new Timer();
16+
appHost.AutoLoadNode.AddChild(this._timer);
17+
18+
this._timer.WaitTime = 0.1;
19+
this._timer.Name = "GodotApiTimer";
20+
this._timer.Timeout += () => this.OnProcess();
21+
this._timer.Start();
22+
}
523
public PackedScene LoadScene(string fileName) {
624
return (PackedScene)this.LoadResource(fileName);
725
}
826

27+
public async Task<PackedScene> LoadSceneAsync(string fileName, Action<double> progress = null) {
28+
return (PackedScene)(await this.LoadResourceAsync(fileName, progress));
29+
}
30+
31+
public async Task<Shader> LoadShaderAsync(string fileName, Action<double> progress = null) {
32+
return (Shader)(await this.LoadResourceAsync(fileName, progress));
33+
}
34+
35+
public Resource LoadResource(string fileName) {
36+
return this.LoadResourceAsync(fileName).Result;
37+
}
38+
39+
public Task<Resource> LoadResourceAsync(string fileName, Action<double> progress = null) {
40+
if (progress != null) {
41+
if(!this._threadedResourceLoadProgress.ContainsKey(fileName)) {
42+
this._threadedResourceLoadProgress[fileName] = new List<Action<double>>();
43+
}
44+
45+
this._threadedResourceLoadProgress[fileName].Add(progress);
46+
}
47+
48+
if (!this._resourceLoadTasks.ContainsKey(fileName)) {
49+
this._resourceLoadTasks[fileName] = Task.Run(() => {
50+
var error = ResourceLoader.LoadThreadedRequest(fileName);
51+
var loadStatus = ResourceLoader.LoadThreadedGetStatus(fileName);
52+
var resourceLoaded = ResourceLoader.LoadThreadedGet(fileName);
53+
54+
return resourceLoaded;
55+
});
56+
}
57+
58+
return this._resourceLoadTasks[fileName];
59+
}
60+
961
public Shader LoadShader(string fileName) {
1062
return (Shader)this.LoadResource(fileName);
1163
}
1264

13-
public Resource LoadResource(string fileName) {
14-
return GD.Load(fileName);
65+
public void OnProcess() {
66+
var keysToRemove = new List<string>();
67+
foreach (var progressInfo in this._threadedResourceLoadProgress) {
68+
var progressArray = new Godot.Collections.Array();
69+
var loadStatus = ResourceLoader.LoadThreadedGetStatus(progressInfo.Key, progressArray);
70+
if (loadStatus != ResourceLoader.ThreadLoadStatus.InProgress) {
71+
if (loadStatus == ResourceLoader.ThreadLoadStatus.Loaded) {
72+
progressInfo.Value.ForEach(x => x(1));
73+
}
74+
75+
keysToRemove.Add(progressInfo.Key);
76+
77+
continue;
78+
}
79+
80+
var progress = progressArray.FirstOrDefault().AsSingle();
81+
progressInfo.Value.ForEach(x => x(progress));
82+
}
83+
84+
foreach (var key in keysToRemove) {
85+
this._threadedResourceLoadProgress.Remove(key);
86+
}
1587
}
1688
}
1789
}

Source/Runtime/IGodotApi.cs

+5
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
1+
using System;
2+
using System.Threading.Tasks;
13
using Godot;
24

35
namespace GoDough.Runtime {
46
public interface IGodotApi {
57
Shader LoadShader(string fileName);
68
PackedScene LoadScene(string fileName);
9+
10+
Task<Shader> LoadShaderAsync(string fileName, Action<double> progress = null);
11+
Task<PackedScene> LoadSceneAsync(string fileName, Action<double> progress = null);
712
}
813
}

Source/Visuals/SceneManagementService.cs

+37-4
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.Collections.ObjectModel;
44
using System.Threading.Tasks;
55
using Godot;
6+
using GoDough.Runtime;
67
using Microsoft.Extensions.Logging;
78

89
// TODO: OnSceneChanging Event - Scene Change blocker?
@@ -17,6 +18,9 @@ public class SceneManagementService<TSceneEnum>
1718
private readonly Dictionary<TSceneEnum, string> _registeredScenes = new Dictionary<TSceneEnum, string>();
1819
private readonly ILogger<SceneManagementService<TSceneEnum>> _logger;
1920
private readonly IAppHostNodeProvider _appHostNodeProvider;
21+
private readonly IGodotApi _godotApi;
22+
23+
private readonly GoDough.Threading.Dispatcher _dispatcher;
2024
#endregion
2125

2226
#region Properties
@@ -32,7 +36,11 @@ public ReadOnlyDictionary<TSceneEnum, string> RegisteredSceneFiles {
3236
#region Ctor
3337
public SceneManagementService(
3438
ILogger<SceneManagementService<TSceneEnum>> logger,
35-
IAppHostNodeProvider appHostNodeProvider) => (_logger, _appHostNodeProvider) = (logger, appHostNodeProvider);
39+
IGodotApi godotApi,
40+
GoDough.Threading.Dispatcher dispatcher,
41+
IAppHostNodeProvider appHostNodeProvider) =>
42+
(_dispatcher, _godotApi, _logger, _appHostNodeProvider) =
43+
(dispatcher, godotApi, logger, appHostNodeProvider);
3644
#endregion
3745

3846
#region Events
@@ -58,14 +66,38 @@ public SceneManagementService<TSceneEnum> RegisterSceneFile(TSceneEnum sceneKey,
5866
return this;
5967
}
6068

61-
public void LoadScene(TSceneEnum sceneKey) {
69+
public async Task LoadScene(TSceneEnum sceneKey, PackedScene loadingScreen = null) {
6270
if (!this._registeredScenes.ContainsKey(sceneKey)) {
6371
throw new KeyNotFoundException(
6472
String.Format(
6573
"Could not find Scene with key '{0}'",
6674
Enum.GetName(typeof(TSceneEnum), sceneKey)));
6775
}
6876

77+
var appHostNode = this._appHostNodeProvider.GetNode();
78+
ProgressBar progressBar = null;
79+
80+
Action<double> progress = loadingProgress =>
81+
this._dispatcher.Invoke(() => {
82+
if(progressBar != null) {
83+
progressBar.Value = loadingProgress;
84+
}
85+
});
86+
87+
if (loadingScreen != null) {
88+
appHostNode
89+
.GetTree()
90+
.ChangeSceneToPacked(loadingScreen);
91+
92+
double progressValue = 0;
93+
94+
while (progressBar == null) {
95+
await appHostNode.ToSignal(appHostNode.GetTree(), "process_frame");
96+
progressBar = appHostNode.GetTree().CurrentScene.GetNode<ProgressBar>("%ProgressBar");
97+
progressBar.Value = progressValue;
98+
}
99+
}
100+
69101
var fileName = this._registeredScenes[sceneKey];
70102
this._logger.LogInformation("Loading Scene '{0}' from '{1}'",
71103
Enum.GetName(typeof(TSceneEnum), sceneKey),
@@ -75,8 +107,9 @@ public void LoadScene(TSceneEnum sceneKey) {
75107
this._logger.LogInformation("_appHostNodeProvider null");
76108
}
77109

78-
var appHostNode = this._appHostNodeProvider.GetNode();
79-
appHostNode.GetTree().ChangeSceneToFile(fileName);
110+
var loadingTask = this._godotApi.LoadSceneAsync(fileName, progress);
111+
112+
appHostNode.GetTree().ChangeSceneToPacked(await loadingTask);
80113

81114
var task = this.WaitForNextFrame(appHostNode, () => {
82115
this.CurrentScene = sceneKey;

0 commit comments

Comments
 (0)