From 07adbf48e0781cd8c95983c1871a84b6160ee5bf Mon Sep 17 00:00:00 2001
From: Loek Le Blansch <loek@pipeframe.xyz>
Date: Thu, 14 Nov 2024 13:57:13 +0100
Subject: implement asset + more WIP audio system

---
 src/crepe/Asset.cpp             | 46 +++++++++++++++++++++++++++++++++++------
 src/crepe/Asset.h               | 32 ++++++++++++++--------------
 src/crepe/api/AudioSource.cpp   |  6 ++----
 src/crepe/api/AudioSource.h     |  5 +++--
 src/crepe/api/CMakeLists.txt    |  4 ++--
 src/crepe/api/Config.h          | 14 +++++++++++++
 src/crepe/api/Texture.cpp       |  2 +-
 src/crepe/facade/SDLContext.cpp |  2 +-
 src/crepe/facade/Sound.cpp      |  2 +-
 9 files changed, 81 insertions(+), 32 deletions(-)

(limited to 'src/crepe')

diff --git a/src/crepe/Asset.cpp b/src/crepe/Asset.cpp
index 9c41ecb..8692c6c 100644
--- a/src/crepe/Asset.cpp
+++ b/src/crepe/Asset.cpp
@@ -1,16 +1,50 @@
 #include <filesystem>
+#include <stdexcept>
+#include <whereami.h>
 
 #include "Asset.h"
+#include "api/Config.h"
 
 using namespace crepe;
 using namespace std;
 
-// FIXME: restore this
-// src(std::filesystem::canonical(src))
-Asset::Asset(const std::string & src) : src(src) {
-	this->file = std::ifstream(this->src, std::ios::in | std::ios::binary);
+Asset::Asset(const string & src) : src(find_asset(src)) { }
+Asset::Asset(const char * src) : src(find_asset(src)) { }
+
+const string & Asset::get_path() const noexcept { return this->src; }
+
+string Asset::find_asset(const string & src) const {
+	auto & cfg = Config::get_instance();
+	auto & root_pattern = cfg.asset.root_pattern;
+
+	// if root_pattern is empty, find_asset must return all paths as-is
+	if (root_pattern.empty()) return src;
+
+	// absolute paths do not need to be resolved, only canonicalized
+	filesystem::path path = src;
+	if (path.is_absolute())
+		return filesystem::canonical(path);
+
+	// find directory matching root_pattern
+	filesystem::path root = this->whereami();
+	while (1) {
+		if (filesystem::exists(root / root_pattern))
+			break;
+		if (!root.has_parent_path())
+			throw runtime_error(format("Asset: Cannot find root pattern ({})", root_pattern));
+		root = root.parent_path();
+	}
+
+	// join path to root (base directory) and canonicalize
+	return filesystem::canonical(root / path);
 }
 
-istream & Asset::get_stream() { return this->file; }
+string Asset::whereami() const noexcept {
+	string path;
+	size_t path_length = wai_getExecutablePath(NULL, 0, NULL);
+	path.resize(path_length + 1); // wai writes null byte
+	wai_getExecutablePath(path.data(), path_length, NULL);
+	path.resize(path_length);
+	return path;
+}
 
-const string & Asset::get_canonical() const { return this->src; }
diff --git a/src/crepe/Asset.h b/src/crepe/Asset.h
index cb413f4..f6e6782 100644
--- a/src/crepe/Asset.h
+++ b/src/crepe/Asset.h
@@ -1,7 +1,5 @@
 #pragma once
 
-#include <fstream>
-#include <iostream>
 #include <string>
 
 namespace crepe {
@@ -9,8 +7,8 @@ namespace crepe {
 /**
  * \brief Asset location helper
  *
- * This class is used to locate and canonicalize paths to game asset files, and
- * should *always* be used when retrieving files from disk.
+ * This class is used to locate game asset files, and should *always* be used
+ * instead of reading file paths directly.
  */
 class Asset {
 public:
@@ -18,24 +16,28 @@ public:
 	 * \param src  Unique identifier to asset
 	 */
 	Asset(const std::string & src);
-
-public:
 	/**
-	 * \brief Get an input stream to the contents of this asset
-	 * \return Input stream with file contents
+	 * \param src  Unique identifier to asset
 	 */
-	std::istream & get_stream();
+	Asset(const char * src);
+
+public:
 	/**
-	 * \brief Get the canonical path to this asset
-	 * \return Canonical path to this asset
+	 * \brief Get the path to this asset
+	 * \return path to this asset
 	 */
-	const std::string & get_canonical() const;
+	const std::string & get_path() const noexcept;
 
 private:
-	//! Canonical path to asset
+	//! path to asset
 	const std::string src;
-	//! File handle (stream)
-	std::ifstream file;
+
+private:
+	std::string find_asset(const std::string & src) const;
+	/**
+	 * \returns The path to the current executable
+	 */
+	std::string whereami() const noexcept;
 };
 
 } // namespace crepe
diff --git a/src/crepe/api/AudioSource.cpp b/src/crepe/api/AudioSource.cpp
index b0cf28c..4baac9a 100644
--- a/src/crepe/api/AudioSource.cpp
+++ b/src/crepe/api/AudioSource.cpp
@@ -1,13 +1,11 @@
-#include <memory>
-
 #include "AudioSource.h"
 
 using namespace crepe;
 using namespace std;
 
-AudioSource::AudioSource(game_object_id_t id, unique_ptr<Asset> audio_clip) :
+AudioSource::AudioSource(game_object_id_t id, const Asset & src) :
 	Component(id),
-	audio_clip(std::move(audio_clip))
+	source(src)
 { }
 
 void AudioSource::play(bool looping) {
diff --git a/src/crepe/api/AudioSource.h b/src/crepe/api/AudioSource.h
index 5bc70f9..0748267 100644
--- a/src/crepe/api/AudioSource.h
+++ b/src/crepe/api/AudioSource.h
@@ -21,8 +21,6 @@ public:
 	void stop();
 
 public:
-	//! Sample file location
-	const std::unique_ptr<Asset> audio_clip;
 	//! Play when this component becomes active
 	bool play_on_awake = false;
 	//! Repeat the current audio clip during playback
@@ -31,6 +29,9 @@ public:
 	float volume = 1.0;
 
 private:
+	//! This audio source's clip
+	const Asset source;
+
 	//! If this source is playing audio
 	bool playing = false;
 	//! Rewind the sample location
diff --git a/src/crepe/api/CMakeLists.txt b/src/crepe/api/CMakeLists.txt
index 85696c4..93a1fac 100644
--- a/src/crepe/api/CMakeLists.txt
+++ b/src/crepe/api/CMakeLists.txt
@@ -1,5 +1,5 @@
 target_sources(crepe PUBLIC
-	# AudioSource.cpp
+	AudioSource.cpp
 	BehaviorScript.cpp
 	Script.cpp
 	GameObject.cpp
@@ -23,7 +23,7 @@ target_sources(crepe PUBLIC
 )
 
 target_sources(crepe PUBLIC FILE_SET HEADERS FILES
-	# AudioSource.h
+	AudioSource.h
 	BehaviorScript.h
 	Config.h
 	Script.h
diff --git a/src/crepe/api/Config.h b/src/crepe/api/Config.h
index e3f86bf..c3f9474 100644
--- a/src/crepe/api/Config.h
+++ b/src/crepe/api/Config.h
@@ -58,6 +58,20 @@ public:
 		 */
 		double gravity = 1;
 	} physics;
+
+	//! Asset loading options
+	struct {
+		/**
+		 * \brief Pattern to match for Asset base directory
+		 *
+		 * All non-absolute paths resolved using \c Asset will be made relative to
+		 * the first parent directory relative to the calling executable where
+		 * appending this pattern results in a path that exists. If this string is
+		 * empty, path resolution is disabled, and Asset will return all paths
+		 * as-is.
+		 */
+		std::string root_pattern = ".crepe-root";
+	} asset;
 };
 
 } // namespace crepe
diff --git a/src/crepe/api/Texture.cpp b/src/crepe/api/Texture.cpp
index de0d0ea..6a1e4d8 100644
--- a/src/crepe/api/Texture.cpp
+++ b/src/crepe/api/Texture.cpp
@@ -26,7 +26,7 @@ Texture::~Texture() {
 
 void Texture::load(unique_ptr<Asset> res) {
 	SDLContext & ctx = SDLContext::get_instance();
-	this->texture = std::move(ctx.texture_from_path(res->get_canonical()));
+	this->texture = std::move(ctx.texture_from_path(res->get_path()));
 }
 
 int Texture::get_width() const {
diff --git a/src/crepe/facade/SDLContext.cpp b/src/crepe/facade/SDLContext.cpp
index a68d940..5527803 100644
--- a/src/crepe/facade/SDLContext.cpp
+++ b/src/crepe/facade/SDLContext.cpp
@@ -159,7 +159,7 @@ SDLContext::texture_from_path(const std::string & path) {
 
 	SDL_Surface * tmp = IMG_Load(path.c_str());
 	if (tmp == nullptr) {
-		tmp = IMG_Load("../asset/texture/ERROR.png");
+		tmp = IMG_Load("asset/texture/ERROR.png");
 	}
 
 	std::unique_ptr<SDL_Surface, std::function<void(SDL_Surface *)>>
diff --git a/src/crepe/facade/Sound.cpp b/src/crepe/facade/Sound.cpp
index 5cd31e8..b7bfeab 100644
--- a/src/crepe/facade/Sound.cpp
+++ b/src/crepe/facade/Sound.cpp
@@ -13,7 +13,7 @@ Sound::Sound(SoundContext & ctx) : context(ctx) { dbg_trace(); }
 
 unique_ptr<Resource> Sound::clone(const Asset & src) const {
 	auto instance = make_unique<Sound>(*this);
-	instance->sample.load(src.get_canonical().c_str());
+	instance->sample.load(src.get_path().c_str());
 	return instance;
 }
 
-- 
cgit v1.2.3