Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
75f1515
initial PoC
Doprez May 8, 2025
2372862
reorganizing code to work with GameStudio
Doprez May 9, 2025
2f7f213
clean up
Doprez May 9, 2025
5e82aa1
fixing input issues
Doprez May 10, 2025
7136214
Separated input
Doprez May 12, 2025
aa3c9fe
clean up and docs
Doprez May 12, 2025
257c458
Kryptos feedback
Doprez May 28, 2025
e83810a
missed some duplicate packages
Doprez May 28, 2025
18397c5
add option for DI in GameFontSystem
Doprez May 29, 2025
2588b5f
Fix error caused by StreamingManager DI.
Doprez May 29, 2025
f1f7b49
renaming to favour ServiceCollection
Doprez May 29, 2025
938a50c
typo
Doprez May 29, 2025
c2de7f4
docs
Doprez May 29, 2025
3b26a45
docs
Doprez May 29, 2025
384dd48
clean up and build logging
Doprez May 29, 2025
ceaec04
remove some hardcoded Game references
Doprez May 30, 2025
8723abe
initial PoC
Doprez May 8, 2025
7590a43
reorganizing code to work with GameStudio
Doprez May 9, 2025
6f02a8b
clean up
Doprez May 9, 2025
9f7bd52
fixing input issues
Doprez May 10, 2025
848bd10
Separated input
Doprez May 12, 2025
731610d
clean up and docs
Doprez May 12, 2025
8c7eaea
Kryptos feedback
Doprez May 28, 2025
415cd6f
missed some duplicate packages
Doprez May 28, 2025
08dc3ce
add option for DI in GameFontSystem
Doprez May 29, 2025
a51d8cf
Fix error caused by StreamingManager DI.
Doprez May 29, 2025
1bcc2e5
renaming to favour ServiceCollection
Doprez May 29, 2025
c5d1c3d
typo
Doprez May 29, 2025
1f8eba6
docs
Doprez May 29, 2025
a315d2b
docs
Doprez May 29, 2025
0ed0012
clean up and build logging
Doprez May 29, 2025
4718339
remove some hardcoded Game references
Doprez May 30, 2025
5d34b28
GameWindowTest
Doprez Jul 20, 2025
c252f6e
rebase
Doprez Jul 20, 2025
850659d
writable ConentManager
Doprez Jul 21, 2025
1432594
Update sources/engine/Stride.Engine/Engine/Builder/GameBuilder.cs
Doprez Nov 23, 2025
4e241b3
Update sources/buildengine/Stride.Core.BuildEngine.Common/Microthread…
Doprez Nov 23, 2025
bdc76e1
Update sources/engine/Stride.Engine/Engine/Builder/MinimalGame.cs
Doprez Nov 23, 2025
0742e53
Update sources/engine/Stride.Engine/Engine/Builder/GameBuilder.cs
Doprez Nov 23, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions sources/Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
<PackageVersion Include="DotRecast.Recast.Toolset" Version="2024.3.1" />
<PackageVersion Include="FFmpeg.AutoGen" Version="3.4.0.2" />
<PackageVersion Include="K4os.Compression.LZ4.Legacy" Version="1.3.6" />
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="9.0.5" />
Copy link

Copilot AI Nov 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Using Microsoft.Extensions.Hosting version 9.0.5. While this is the latest version, please verify this version is compatible with all target frameworks and platforms that Stride supports (including mobile platforms like Android and iOS). Note that version 9.x may have different requirements than earlier versions.

Suggested change
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="9.0.5" />
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="8.0.0" />

Copilot uses AI. Check for mistakes.
<PackageVersion Include="Microsoft.Management.Infrastructure" Version="3.0.0-preview.4" />
<PackageVersion Include="Microsoft.NETCore.Platforms" Version="7.0.4" />
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="8.0.0" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,10 @@ private static DatabaseFileProvider CreateDatabase(BuildTransaction transaction)

private class MicroThreadLocalProviderService : IDatabaseFileProviderService
{
public DatabaseFileProvider FileProvider => MicroThreadLocalDatabaseFileProvider.Value;
public DatabaseFileProvider FileProvider
{
get => MicroThreadLocalDatabaseFileProvider.Value;
set => MicroThreadLocalDatabaseFileProvider.Value = value;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ namespace Stride.Core.IO;

public interface IDatabaseFileProviderService
{
DatabaseFileProvider FileProvider { get; }
}
DatabaseFileProvider FileProvider { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,19 @@ public interface IContentManager
/// <returns>A stream to the raw asset.</returns>
Stream OpenAsStream(string url, StreamFlags streamFlags = StreamFlags.None);

/// <summary>
/// Saves an asset at a specific URL.
/// </summary>
/// <param name="url">The URL.</param>
/// <param name="asset">The asset.</param>
/// <param name="storageType">The custom storage type to use. Use null as default.</param>
/// <exception cref="System.ArgumentNullException">
/// url
/// or
/// asset
/// </exception>
void Save(string url, object asset, Type? storageType);

/// <summary>
/// Loads content from the specified URL.
/// </summary>
Expand Down
9 changes: 9 additions & 0 deletions sources/core/Stride.Core/IServiceRegistry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,15 @@ public interface IServiceRegistry
/// <exception cref="ArgumentException">Thrown when a service of the same type is already registered.</exception>
void AddService<T>(T service) where T : class;

/// <summary>
/// Adds a service to this <see cref="ServiceRegistry"/>.
/// </summary>
/// <param name="service">The service to add.</param>
/// <param name="type">The type to register as.</param>
/// <exception cref="ArgumentNullException">Thrown when the provided service is null.</exception>
/// <exception cref="ArgumentException">Thrown when a service of the same type is already registered.</exception>
void AddService(object service, Type type);

/// <summary>
/// Gets the service object of the specified type.
/// </summary>
Expand Down
16 changes: 16 additions & 0 deletions sources/core/Stride.Core/ServiceRegistry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,22 @@ public void AddService<T>(T service)
OnServiceAdded(new ServiceEventArgs(type, service));
}

/// <inheritdoc />
/// <remarks>
/// This implementation triggers the <see cref="ServiceAdded"/> event after a service is successfully added.
/// </remarks>
public void AddService(object service, Type type)
{
ArgumentNullException.ThrowIfNull(service);

lock (registeredService)
{
if (!registeredService.TryAdd(type, service))
throw new ArgumentException("Service is already registered with this type", nameof(type));
}
OnServiceAdded(new ServiceEventArgs(type, service));
}

/// <inheritdoc />
/// <remarks>
/// This implementation triggers the <see cref="ServiceRemoved"/> event after a service is successfully removed.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ public abstract class EntityHierarchyEditorGame : EditorServiceGame
private Material fallbackColorMaterial;
private Material fallbackTextureMaterial;

protected EntityHierarchyEditorGame(TaskCompletionSource<bool> gameContentLoadedTaskSource, IEffectCompiler effectCompiler, string effectLogPath)
protected EntityHierarchyEditorGame(TaskCompletionSource<bool> gameContentLoadedTaskSource, IEffectCompiler effectCompiler, string effectLogPath, GameContext context = null)
: base(context)
{
this.gameContentLoadedTaskSource = gameContentLoadedTaskSource;
this.effectCompiler = effectCompiler;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,11 @@ public bool IsEditorHidden

public event EventHandler<ExceptionThrownEventArgs> ExceptionThrown;

public EditorServiceGame(GameContext context) : base(context)
{

}

/// <summary>
/// Calculates and returns the position of the mouse in the scene.
/// </summary>
Expand Down
3 changes: 2 additions & 1 deletion sources/editor/Stride.Editor/Engine/EmbeddedGame.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using Stride.Core.Diagnostics;
using Stride.Engine;
using Stride.Games;
using Stride.Graphics;

namespace Stride.Editor.Engine
Expand All @@ -17,7 +18,7 @@ public class EmbeddedGame : Game
/// </summary>
public static bool DebugMode { get; set; }

public EmbeddedGame()
public EmbeddedGame(GameContext context) : base(context)
{
GraphicsDeviceManager.PreferredGraphicsProfile = new [] { GraphicsProfile.Level_11_0, GraphicsProfile.Level_10_1, GraphicsProfile.Level_10_0 };
GraphicsDeviceManager.PreferredBackBufferWidth = 64;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,8 +137,8 @@ private void StrideUIThread()

initializationSignal.Set();

PreviewGame = new PreviewGame(AssetBuilderService.EffectCompiler);
var context = new GameContextWinforms(gameForm) { InitializeDatabase = false };
PreviewGame = new PreviewGame(AssetBuilderService.EffectCompiler, context);

// Wait for shaders to be loaded
AssetBuilderService.WaitForShaders();
Expand Down
11 changes: 6 additions & 5 deletions sources/editor/Stride.Editor/Preview/PreviewGame.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
using System;
using System.Threading.Tasks;
using Stride.Core.BuildEngine;
using Stride.Core;
using Stride.Core.Diagnostics;
using Stride.Core.Mathematics;
using Stride.Assets;
using Stride.Assets.SpriteFont;
using Stride.Assets.SpriteFont.Compiler;
using Stride.Core;
using Stride.Core.BuildEngine;
using Stride.Core.Diagnostics;
using Stride.Core.Mathematics;
using Stride.Engine;
using Stride.Engine.Design;
using Stride.Games;
using Stride.Graphics;
using Stride.Rendering.Compositing;
using Stride.Shaders.Compiler;
Expand Down Expand Up @@ -43,7 +44,7 @@ public class PreviewGame : EditorGame.Game.EditorServiceGame

private Scene previewScene;

public PreviewGame(IEffectCompiler effectCompiler)
public PreviewGame(IEffectCompiler effectCompiler, GameContext context) : base(context)
{
this.effectCompiler = effectCompiler;
}
Expand Down
11 changes: 6 additions & 5 deletions sources/engine/Stride.Debugger/Debugger/LiveAssemblyReloader.cs
Original file line number Diff line number Diff line change
@@ -1,29 +1,30 @@
// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)
// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Stride.Core;
using Stride.Core.Reflection;
using Stride.Core.Serialization;
using Stride.Core.Yaml;
using Stride.Core.Yaml.Events;
using Stride.Core.Yaml.Serialization;
using Stride.Debugger.Target;
using Stride.Engine;
using Stride.Games;

namespace Stride.Debugger
{
public static class LiveAssemblyReloader
{
public static void Reload(Game game, AssemblyContainer assemblyContainer, List<Assembly> assembliesToUnregister, List<Assembly> assembliesToRegister)
public static void Reload(GameBase game, AssemblyContainer assemblyContainer, List<Assembly> assembliesToUnregister, List<Assembly> assembliesToRegister)
{
List<Entity> entities = new List<Entity>();

var sceneSystem = game.Services.GetSafeServiceAs<SceneSystem>();

if (game != null)
entities.AddRange(game.SceneSystem.SceneInstance);
entities.AddRange(sceneSystem.SceneInstance);

CloneReferenceSerializer.References = new List<object>();

Expand Down
151 changes: 151 additions & 0 deletions sources/engine/Stride.Engine/Engine/Builder/GameBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
using System;
using System.Collections.Generic;
using Microsoft.Extensions.DependencyInjection;
using Stride.Core;
using Stride.Core.Diagnostics;
using Stride.Games;
using Stride.Input;

namespace Stride.Engine.Builder;

/// <summary>
/// Helps build the game and preps it to be able to run after <see cref="Build"/>.
/// </summary>
public class GameBuilder : IGameBuilder
{
/// <summary>
/// This is used to allow the same instance to be registered multiple times as different interfaces or types. This was done due to how <see cref="IServiceRegistry"/> works."/>
/// </summary>
public Dictionary<Type, object> InternalServices { get; internal set; } = [];

/// <summary>
/// This allows for Service to be registered through DI.
/// </summary>
public IServiceCollection Services { get; internal set; } = new ServiceCollection();

/// <summary>
/// This is a direct reference to the game systems collection of the <see cref="GameBase"/>.
/// </summary>
public GameSystemCollection GameSystems { get; internal set; }

/// <summary>
/// Adds log listeners to the game on <see cref="Build"/>. This is registered first so it will log build errors if they occur."/>
Copy link

Copilot AI Nov 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Documentation has incorrect closing tag. Should use </summary> instead of "/>.

Suggested change
/// Adds log listeners to the game on <see cref="Build"/>. This is registered first so it will log build errors if they occur."/>
/// Adds log listeners to the game on <see cref="Build"/>. This is registered first so it will log build errors if they occur.

Copilot uses AI. Check for mistakes.
/// </summary>
public List<LogListener> LogListeners { get; internal set; } = [];

/// <summary>
/// Adds input sources to the game on <see cref="Build"/>.
/// </summary>
public List<IInputSource> InputSources { get; internal set; } = [];

public GameBase Game { get; set; }

public GameContext Context { get; set; }

private static Logger _log => GlobalLogger.GetLogger(nameof(GameBuilder));

internal GameBuilder(GameBase game)
{
Game = game ?? new MinimalGame(null);
GameSystems = Game.GameSystems;
Services.AddSingleton<IServiceRegistry>(Game.Services);
InternalServices.Add(typeof(IServiceRegistry), Game.Services);
}

/// <summary>
/// Creates a new instance of the <see cref="GameBuilder"/> class.
/// </summary>
/// <returns></returns>
public static GameBuilder Create(GameBase game = null)
{
return new GameBuilder(game);
}

public virtual GameBase Build()
{
foreach (var logListener in LogListeners)
{
GlobalLogger.GlobalMessageLogged += logListener;
}

var provider = Services.BuildServiceProvider();
foreach (var service in InternalServices)
{
if (service.Key == typeof(IServiceRegistry) || service.Key == typeof(IServiceProvider))
continue;

try
{
if (service.Value == null)
{
var instance = provider.GetService(service.Key);

if(instance == null)
{
//check if the type is inherited from another instance in the services.
foreach (var kvp in InternalServices)
{
if (kvp.Key.IsAssignableFrom(service.Key) && kvp.Value != null)
{
instance = provider.GetService(kvp.Key);
if(instance is not null)
break;
}
}
}

_log.Info($"Registering service {service.Key.Name}.");
Game.Services.AddService(instance, service.Key);
InternalServices[service.Key] = instance;
Comment on lines +97 to +99
Copy link

Copilot AI Nov 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential null reference exception. When instance is null after both checks (lines 82-96), calling Game.Services.AddService(instance, service.Key) on line 99 will pass a null value which may cause issues. Consider logging a warning or skipping the service registration when the instance cannot be resolved.

Suggested change
_log.Info($"Registering service {service.Key.Name}.");
Game.Services.AddService(instance, service.Key);
InternalServices[service.Key] = instance;
if (instance != null)
{
_log.Info($"Registering service {service.Key.Name}.");
Game.Services.AddService(instance, service.Key);
InternalServices[service.Key] = instance;
}
else
{
_log.Warning($"Could not resolve service {service.Key.Name}. Skipping registration.");
}

Copilot uses AI. Check for mistakes.
}
else
{
_log.Info($"Registering service {service.Key.Name}.");
Game.Services.AddService(service.Value, service.Key);
}
}
catch (Exception ex)
{
// TODO: check if service is already registered first.
_log.Error($"Failed to register service {service.Key.Name}.\n\n", ex);
}
}

// Add all game systems to the game.
foreach (var service in InternalServices)
{
var system = provider.GetService(service.Key);
if (system is IGameSystemBase gameSystem && !Game.GameSystems.Contains(gameSystem))
{
_log.Info($"Adding game system {gameSystem.GetType().Name} to the game systems collection.");
Game.GameSystems.Add(gameSystem);
}
}

if (Context != null)
{
_log.Info($"Setting game context.");
Game.SetGameContext(Context);
}

if(InputSources.Count > 0)
{
var inputManager = Game.Services.GetService<InputManager>();

if (inputManager is null)
{
_log.Info("No InputManager found in the game services, creating default.");
inputManager = new InputManager();
Game.Services.AddService(inputManager);
}

foreach (var inputSource in InputSources)
{
_log.Info($"Adding input source {inputSource.GetType().Name} to the input manager.");
inputManager.Sources.Add(inputSource);
}
}

return Game;
}
}
Loading