1 /** content/asset loader and manager */ 2 3 module re.content; 4 5 import std.string; 6 import std.file; 7 import std.conv; 8 import std.path; 9 import std.stdio; 10 import std.exception : enforce; 11 import optional; 12 13 import re.util.cache; 14 import re.util.interop; 15 static import raylib; 16 17 /// manages external content loading 18 class ContentManager { 19 /// search paths for content 20 public string[] paths; 21 22 alias Texture2D = raylib.Texture2D; 23 alias Model = raylib.Model; 24 alias Shader = raylib.Shader; 25 alias Music = raylib.Music; 26 27 private KeyedCache!Texture2D texture_cache; 28 private KeyedCache!Model model_cache; 29 private KeyedCache!Shader shader_cache; 30 private KeyedCache!Music music_cache; 31 32 /// initializes the content manager 33 this() { 34 // setup caches 35 texture_cache = KeyedCache!Texture2D(tex => raylib.UnloadTexture(tex)); 36 model_cache = KeyedCache!Model(mdl => raylib.UnloadModel(mdl)); 37 shader_cache = KeyedCache!Shader(shd => raylib.UnloadShader(shd)); 38 music_cache = KeyedCache!Music(mus => raylib.UnloadMusicStream(mus)); 39 } 40 41 /// get the physical path to a logical content path 42 public string get_path(string path) { 43 // check if this is already a valid path 44 if (std.file.exists(path)) { 45 return path; 46 } 47 auto base = string.init; 48 alias join_paths = std.path.buildNormalizedPath; 49 // check search paths first 50 foreach (search_path; paths) { 51 // if the combination path exists, then make this base 52 if (std.file.exists(join_paths(search_path, path))) { 53 base = search_path; 54 break; 55 } 56 } 57 return join_paths(base, path); 58 } 59 60 private char* get_path_cstr(string path) { 61 return get_path(path).c_str; 62 } 63 64 private KeyedCache!T cache_for(T)() { 65 static if (is(T == Texture2D)) { 66 return texture_cache; 67 } else static if (is(T == Model)) { 68 return model_cache; 69 } else static if (is(T == Shader)) { 70 return shader_cache; 71 } else static if (is(T == Music)) { 72 return music_cache; 73 } else { 74 static assert(0, format("no cache found for type %s", typeof(T))); 75 } 76 } 77 78 private Optional!T load_cached_asset(T)(string path, T delegate(string) load_func) { 79 auto cache = cache_for!T(); 80 auto cached = cache.get(path); 81 if (cached.isNull) { 82 auto real_path = get_path(path); 83 if (!exists(real_path)) { 84 return no!T; 85 } 86 auto asset = load_func(real_path); 87 cache.put(path, asset); 88 return some(asset); 89 } else { 90 auto asset = cached.get; 91 return some(asset); 92 } 93 } 94 95 /// loads a texture from disk 96 public Optional!Texture2D load_texture2d(string path) { 97 return load_cached_asset!Texture2D(path, (x) => raylib.LoadTexture(x.c_str)); 98 } 99 100 /// loads a model from disk 101 public Optional!Model load_model(string path) { 102 return load_cached_asset!Model(path, (x) => raylib.LoadModel(x.c_str)); 103 } 104 105 // public raylib.ModelAnimation[] load_model_animations(string path) { 106 // uint num_loaded_anims = 0; 107 // raylib.ModelAnimation* loaded_anims = raylib.LoadModelAnimations(get_path_cstr(path), &num_loaded_anims); 108 // auto anims = loaded_anims[0 .. num_loaded_anims]; // access array as slice 109 // return anims; 110 // } 111 112 /// loads a shader from disk (vertex shader, fragment shader). 113 /// pass null to either arg to use the default 114 /// since loading shaders is a bit of a pain, the loader uses custom logic 115 public Optional!Shader load_shader(string vs_path, string fs_path, bool bypass_cache = false) { 116 raylib.Shader shd; 117 import std.digest.sha : sha1Of, toHexString; 118 119 auto path_hash = to!string(sha1Of(vs_path ~ fs_path).toHexString); 120 auto cache = cache_for!Shader(); 121 auto cached = cache.get(path_hash); 122 if (cached.isNull || bypass_cache) { 123 auto vs_real_path = get_path(vs_path); 124 auto fs_real_path = get_path(fs_path); 125 if (!exists(vs_real_path) && !exists(fs_real_path)) { 126 // neither path exists 127 return no!Shader; 128 } 129 auto vs = vs_path.length > 0 ? get_path_cstr(vs_path) : null; 130 auto fs = fs_path.length > 0 ? get_path_cstr(fs_path) : null; 131 shd = raylib.LoadShader(vs, fs); 132 if (!bypass_cache) 133 cache.put(path_hash, shd); 134 } else { 135 shd = cached.get; 136 } 137 return some(shd); 138 } 139 140 /// loads music from disk 141 public Optional!Music load_music(string file_path) { 142 return load_cached_asset!Music(file_path, (x) => raylib.LoadMusicStream(x.c_str)); 143 } 144 145 public void drop_caches() { 146 cache_for!Texture2D().drop(); 147 cache_for!Model().drop(); 148 cache_for!Shader().drop(); 149 cache_for!Music().drop(); 150 } 151 152 /// releases all resources 153 public void destroy() { 154 drop_caches(); 155 } 156 }