Skip to content

Commit 7399ce1

Browse files
committed
feat: enhance asset management with flexible path configuration and shader integration
1 parent 66beea1 commit 7399ce1

3 files changed

Lines changed: 230 additions & 16 deletions

File tree

samples/SampleGame/AssetDemo.cs

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using Rac.Engine;
44
using Rac.Assets.Types;
55
using Rac.Rendering;
6+
using Rac.Rendering.Shader;
67
using Rac.Core.Manager;
78
using Rac.Input.Service;
89
using Silk.NET.Maths;
@@ -30,15 +31,14 @@ public class AssetDemo
3031
private IEngineFacade? _engine;
3132
private Texture? _sampleTexture;
3233
private AudioClip? _sampleAudio;
33-
private IAssetService? _assetService;
3434

35-
// Square geometry for rendering
36-
private readonly BasicVertex[] _squareVertices = new[]
35+
// Square geometry for texture rendering with proper UV coordinates
36+
private readonly TexturedVertex[] _squareVertices = new[]
3737
{
38-
new BasicVertex(new Vector2D<float>(-100, -100)), // Bottom-left
39-
new BasicVertex(new Vector2D<float>( 100, -100)), // Bottom-right
40-
new BasicVertex(new Vector2D<float>( 100, 100)), // Top-right
41-
new BasicVertex(new Vector2D<float>(-100, 100)) // Top-left
38+
new TexturedVertex(new Vector2D<float>(-100, -100), new Vector2D<float>(0, 1)), // Bottom-left (UV: 0,1)
39+
new TexturedVertex(new Vector2D<float>( 100, -100), new Vector2D<float>(1, 1)), // Bottom-right (UV: 1,1)
40+
new TexturedVertex(new Vector2D<float>( 100, 100), new Vector2D<float>(1, 0)), // Top-right (UV: 1,0)
41+
new TexturedVertex(new Vector2D<float>(-100, 100), new Vector2D<float>(0, 0)) // Top-left (UV: 0,0)
4242
};
4343

4444
public static void Run(string[] args)
@@ -127,9 +127,7 @@ private void LoadAssets()
127127

128128
// Load audio using Engine facade (recommended approach)
129129
Console.WriteLine("Loading SampleAudio.wav...");
130-
var _path = Path.Combine(AppContext.BaseDirectory, "Assets");
131-
var _fullPath = Path.Combine(_path, "SampleAudio.wav");
132-
if (_engine != null) _sampleAudio = _engine.LoadAudio(_fullPath);
130+
_sampleAudio = _engine.LoadAudio("SampleAudio.wav");
133131
Console.WriteLine($"✓ Audio loaded: {_sampleAudio.Duration:F2}s, {_sampleAudio.SampleRate}Hz, {_sampleAudio.Channels} channels, {_sampleAudio.MemorySize / 1024}KB");
134132

135133
// Demonstrate asset service integration
@@ -191,6 +189,9 @@ private void OnRender(float deltaTime)
191189
// Clear the screen
192190
_engine.Renderer.Clear();
193191

192+
// Set shader mode for rendering (this was the missing piece!)
193+
_engine.Renderer.SetShaderMode(ShaderMode.Normal);
194+
194195
// Render textured square in the center of the screen
195196
if (_sampleTexture != null)
196197
{

samples/SampleGame/Program.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ public static class Program
1010
["camerademo"] = ("Interactive camera system demonstration with dual-camera rendering", CameraDemonstration.Run),
1111
["pipelinedemo"] = ("Educational 4-phase rendering pipeline demonstration", RenderingPipelineDemo.Run),
1212
["containersample"] = ("Container system demonstration with inventory and equipment patterns", ContainerSample.Run),
13-
["assetdemo"] = ("Assets system demonstration", AssetDemo.Run)
13+
["assetdemo"] = ("Assets system demonstration", AssetDemo.Run),
14+
["assetpathdemo"] = ("Asset path configuration demonstration", AssetPathDemo.Run)
1415
};
1516

1617
public static void Main(string[] args)

src/Rac.Engine/EngineFacade.cs

Lines changed: 217 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
using Rac.Rendering.Camera;
1515
using Silk.NET.Input;
1616
using Silk.NET.Maths;
17+
using System.Collections.Concurrent;
1718

1819
namespace Rac.Engine;
1920

@@ -22,6 +23,9 @@ public class EngineFacade : IEngineFacade
2223
private readonly GameEngine.Engine _inner;
2324
private readonly IWindowManager _windowManager;
2425
private readonly TransformSystem _transformSystem;
26+
private readonly ConcurrentDictionary<Type, FileAssetService> _assetServices = new();
27+
private readonly Dictionary<Type, string> _assetTypePaths = new();
28+
private string _defaultAssetBasePath;
2529

2630
public EngineFacade(
2731
IWindowManager windowManager,
@@ -49,9 +53,12 @@ ConfigManager configManager
4953
Audio = new NullAudioService();
5054
}
5155

56+
// Initialize default asset base path
57+
_defaultAssetBasePath = Path.Combine(AppContext.BaseDirectory, "Assets");
58+
5259
// Initialize asset service with default configuration
5360
Assets = AssetServiceBuilder.Create()
54-
.WithBasePath("Assets")
61+
.WithBasePath(_defaultAssetBasePath)
5562
.Build();
5663

5764
// Initialize transform system (required for container operations)
@@ -92,7 +99,7 @@ ConfigManager configManager
9299
public SystemScheduler Systems { get; }
93100
public IRenderer Renderer => _inner.Renderer;
94101
public IAudioService Audio { get; }
95-
public IAssetService Assets { get; }
102+
public IAssetService Assets { get; private set; }
96103
public ICameraManager CameraManager { get; }
97104
public IWindowManager WindowManager => _windowManager;
98105
public IContainerService Container { get; }
@@ -326,7 +333,7 @@ public Texture LoadTexture(string filename)
326333

327334
try
328335
{
329-
return Assets.LoadAsset<Texture>(filename);
336+
return GetAssetServiceForType<Texture>().LoadAsset<Texture>(filename);
330337
}
331338
catch (FileNotFoundException)
332339
{
@@ -390,7 +397,7 @@ public AudioClip LoadAudio(string filename)
390397

391398
try
392399
{
393-
return Assets.LoadAsset<AudioClip>(filename);
400+
return GetAssetServiceForType<AudioClip>().LoadAsset<AudioClip>(filename);
394401
}
395402
catch (FileNotFoundException)
396403
{
@@ -456,7 +463,7 @@ public string LoadShaderSource(string filename)
456463

457464
try
458465
{
459-
return Assets.LoadAsset<string>(filename);
466+
return GetAssetServiceForType<string>().LoadAsset<string>(filename);
460467
}
461468
catch (FileNotFoundException)
462469
{
@@ -472,4 +479,209 @@ public string LoadShaderSource(string filename)
472479
$"Ensure the file uses a supported text encoding (UTF-8 recommended).", ex);
473480
}
474481
}
482+
483+
// ═══════════════════════════════════════════════════════════════════════════
484+
// ASSET PATH CONFIGURATION METHODS
485+
// ═══════════════════════════════════════════════════════════════════════════
486+
487+
/// <summary>
488+
/// Sets the base path for all asset types (fallback for specific types).
489+
///
490+
/// EDUCATIONAL PURPOSE:
491+
/// This method demonstrates flexible asset organization:
492+
/// - Allows changing the default asset location at runtime
493+
/// - Provides fallback path for asset types without specific configuration
494+
/// - Recreates asset services to use the new path
495+
/// - Maintains backward compatibility with existing code
496+
/// </summary>
497+
/// <param name="basePath">Base directory for asset files</param>
498+
/// <exception cref="ArgumentNullException">Thrown when basePath is null</exception>
499+
/// <example>
500+
/// <code>
501+
/// // Set all assets to load from "GameContent" folder
502+
/// engine.SetAssetBasePath("GameContent");
503+
///
504+
/// // Set absolute path
505+
/// engine.SetAssetBasePath(@"C:\MyGame\Assets");
506+
/// </code>
507+
/// </example>
508+
public void SetAssetBasePath(string basePath)
509+
{
510+
if (basePath == null)
511+
throw new ArgumentNullException(nameof(basePath));
512+
513+
_defaultAssetBasePath = Path.IsPathRooted(basePath)
514+
? basePath
515+
: Path.Combine(AppContext.BaseDirectory, basePath);
516+
517+
RecreateAssetServices();
518+
}
519+
520+
/// <summary>
521+
/// Sets the base path specifically for audio assets.
522+
///
523+
/// EDUCATIONAL PURPOSE:
524+
/// Type-specific asset organization enables better project structure:
525+
/// - Audio files can be organized in dedicated folders
526+
/// - Different asset types can have different storage strategies
527+
/// - Fallback to default path ensures compatibility
528+
/// - Runtime configuration allows flexible deployment
529+
/// </summary>
530+
/// <param name="basePath">Base directory for audio asset files</param>
531+
/// <exception cref="ArgumentNullException">Thrown when basePath is null</exception>
532+
/// <example>
533+
/// <code>
534+
/// // Audio files in "Audio" folder
535+
/// engine.SetAudioBasePath("Audio");
536+
///
537+
/// // Then load audio normally
538+
/// var sound = engine.LoadAudio("jump.wav"); // Loads from Audio/jump.wav
539+
/// </code>
540+
/// </example>
541+
public void SetAudioBasePath(string basePath)
542+
{
543+
if (basePath == null)
544+
throw new ArgumentNullException(nameof(basePath));
545+
546+
var fullPath = Path.IsPathRooted(basePath)
547+
? basePath
548+
: Path.Combine(AppContext.BaseDirectory, basePath);
549+
550+
_assetTypePaths[typeof(AudioClip)] = fullPath;
551+
RecreateAssetServiceForType<AudioClip>();
552+
}
553+
554+
/// <summary>
555+
/// Sets the base path specifically for texture assets.
556+
///
557+
/// EDUCATIONAL PURPOSE:
558+
/// Texture-specific organization demonstrates asset categorization:
559+
/// - Visual assets often have different storage requirements
560+
/// - Texture files can be large and benefit from dedicated organization
561+
/// - Separate paths enable different caching strategies
562+
/// - Artist-friendly folder structures improve workflow
563+
/// </summary>
564+
/// <param name="basePath">Base directory for texture asset files</param>
565+
/// <exception cref="ArgumentNullException">Thrown when basePath is null</exception>
566+
/// <example>
567+
/// <code>
568+
/// // Texture files in "Textures" folder
569+
/// engine.SetTextureBasePath("Textures");
570+
///
571+
/// // Then load textures normally
572+
/// var texture = engine.LoadTexture("player.png"); // Loads from Textures/player.png
573+
/// </code>
574+
/// </example>
575+
public void SetTextureBasePath(string basePath)
576+
{
577+
if (basePath == null)
578+
throw new ArgumentNullException(nameof(basePath));
579+
580+
var fullPath = Path.IsPathRooted(basePath)
581+
? basePath
582+
: Path.Combine(AppContext.BaseDirectory, basePath);
583+
584+
_assetTypePaths[typeof(Texture)] = fullPath;
585+
RecreateAssetServiceForType<Texture>();
586+
}
587+
588+
/// <summary>
589+
/// Sets the base path specifically for text assets (shaders, configs, etc.).
590+
///
591+
/// EDUCATIONAL PURPOSE:
592+
/// Text asset organization enables data-driven development:
593+
/// - Shader files can be organized separately from other assets
594+
/// - Configuration files can have dedicated storage
595+
/// - Text assets often have different versioning requirements
596+
/// - Enables separate processing pipelines for different text types
597+
/// </summary>
598+
/// <param name="basePath">Base directory for text asset files</param>
599+
/// <exception cref="ArgumentNullException">Thrown when basePath is null</exception>
600+
/// <example>
601+
/// <code>
602+
/// // Text files in "Data" folder
603+
/// engine.SetTextBasePath("Data");
604+
///
605+
/// // Then load text assets normally
606+
/// var shader = engine.LoadShaderSource("basic.vert"); // Loads from Data/basic.vert
607+
/// </code>
608+
/// </example>
609+
public void SetTextBasePath(string basePath)
610+
{
611+
if (basePath == null)
612+
throw new ArgumentNullException(nameof(basePath));
613+
614+
var fullPath = Path.IsPathRooted(basePath)
615+
? basePath
616+
: Path.Combine(AppContext.BaseDirectory, basePath);
617+
618+
_assetTypePaths[typeof(string)] = fullPath;
619+
RecreateAssetServiceForType<string>();
620+
}
621+
622+
/// <summary>
623+
/// Recreates all asset services to use updated base paths.
624+
/// Educational note: Ensures all services use current path configuration.
625+
/// </summary>
626+
private void RecreateAssetServices()
627+
{
628+
// Dispose existing services
629+
foreach (var service in _assetServices.Values)
630+
{
631+
service.Dispose();
632+
}
633+
_assetServices.Clear();
634+
635+
// Recreate main asset service
636+
if (Assets is IDisposable disposableAssets)
637+
{
638+
disposableAssets.Dispose();
639+
}
640+
Assets = AssetServiceBuilder.Create()
641+
.WithBasePath(_defaultAssetBasePath)
642+
.Build();
643+
}
644+
645+
/// <summary>
646+
/// Recreates the asset service for a specific type.
647+
/// Educational note: Selective recreation minimizes impact on other asset types.
648+
/// </summary>
649+
private void RecreateAssetServiceForType<T>() where T : class
650+
{
651+
var type = typeof(T);
652+
653+
if (_assetServices.TryRemove(type, out var existingService))
654+
{
655+
existingService.Dispose();
656+
}
657+
658+
// The service will be recreated lazily when needed
659+
}
660+
661+
/// <summary>
662+
/// Gets the appropriate asset service for the specified type.
663+
/// Educational note: Lazy initialization with type-specific path resolution.
664+
/// </summary>
665+
private IAssetService GetAssetServiceForType<T>() where T : class
666+
{
667+
var type = typeof(T);
668+
669+
if (_assetServices.TryGetValue(type, out var existingService))
670+
{
671+
return existingService;
672+
}
673+
674+
// Determine base path for this type
675+
var basePath = _assetTypePaths.TryGetValue(type, out var specificPath)
676+
? specificPath
677+
: _defaultAssetBasePath;
678+
679+
// Create service for this type
680+
var service = AssetServiceBuilder.Create()
681+
.WithBasePath(basePath)
682+
.Build();
683+
684+
_assetServices[type] = service;
685+
return service;
686+
}
475687
}

0 commit comments

Comments
 (0)