diff --git a/BUILD.gn b/BUILD.gn index 4f31329..d098093 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -58,6 +58,8 @@ executable("aquarium") { "src/common/AQUARIUM_ASSERT.h", "src/common/FPSTimer.cpp", "src/common/FPSTimer.h", + "src/common/Path.cpp", + "src/common/Path.h", ] deps = [ @@ -309,6 +311,8 @@ executable("aquarium-direct-map") { "src/common/AQUARIUM_ASSERT.h", "src/common/FPSTimer.cpp", "src/common/FPSTimer.h", + "src/common/Path.cpp", + "src/common/Path.h", ] deps = [ diff --git a/src/aquarium-direct-map/Globals.h b/src/aquarium-direct-map/Globals.h index 7b596fa..254c52e 100644 --- a/src/aquarium-direct-map/Globals.h +++ b/src/aquarium-direct-map/Globals.h @@ -21,12 +21,8 @@ #include "common/FPSTimer.h" #if defined(OS_WIN) -const std::string slash = "\\"; #define M_PI 3.141592653589793 #endif -#if defined(OS_MAC) || (defined(OS_LINUX) && !defined(OS_CHROMEOS)) -const std::string slash = "/"; -#endif class Program; class Scene; diff --git a/src/aquarium-direct-map/Main.cpp b/src/aquarium-direct-map/Main.cpp index f962b69..65431c0 100644 --- a/src/aquarium-direct-map/Main.cpp +++ b/src/aquarium-direct-map/Main.cpp @@ -12,7 +12,6 @@ #include #include #include -#include #include #include @@ -30,18 +29,13 @@ #include "Model.h" #include "Program.h" #include "common/AQUARIUM_ASSERT.h" +#include "common/Path.h" #include "include/CmdArgsHelper.h" #if defined(OS_WIN) #include -#include #endif -#if defined(OS_MAC) -#include -#include -#endif -#if defined(OS_LINUX) && !defined(OS_CHROMEOS) -#include +#if defined(OS_MAC) || (defined(OS_LINUX) && !defined(OS_CHROMEOS)) #include #endif @@ -55,7 +49,7 @@ int clientWidth; int clientHeight; // Get current path of the binary -std::string mPath; +Path mPath = Path::getExecutablePath().pop().pop().pop(); // The number of fish is passed from cmd args directly int g_numFish; @@ -260,9 +254,7 @@ void initializeGlobalInfo() { // Load json file from assets. Initialize g_sceneGroups and classify groups. void LoadPlacement() { - std::ostringstream oss; - oss << mPath << resourceFolder << slash << "PropPlacement.js"; - std::string proppath = oss.str(); + Path proppath = Path(mPath).push(resourceFolder).push("PropPlacement.js"); std::ifstream PlacementStream(proppath, std::ios::in); rapidjson::IStreamWrapper isPlacement(PlacementStream); rapidjson::Document document; @@ -332,36 +324,7 @@ void onDestroy() { } } -void getCurrentPath() { - // Get path of current build. -#if defined(OS_WIN) - TCHAR temp[200]; - GetModuleFileName(NULL, temp, MAX_PATH); - std::wstring ws(temp); - mPath = std::string(ws.begin(), ws.end()); - size_t nPos = mPath.find_last_of(slash); - mPath = mPath.substr(0, nPos) + slash + ".." + slash + ".." + slash; -#elif defined(OS_MAC) - char temp[200]; - uint32_t size = sizeof(temp); - _NSGetExecutablePath(temp, &size); - mPath = std::string(temp); - int nPos = mPath.find_last_of(slash); - mPath = mPath.substr(0, nPos) + slash + ".." + slash + ".." + slash; -#elif defined(OS_LINUX) && !defined(OS_CHROMEOS) - char temp[200]; - readlink("/proc/self/exe", temp, sizeof(temp)); - mPath = std::string(temp); - int nPos = mPath.find_last_of(slash); - mPath = mPath.substr(0, nPos) + slash + ".." + slash + ".." + slash; -#else - ASSERT(false); -#endif -} - bool initialize(int argc, char **argv) { - getCurrentPath(); - glEnable(GL_DEPTH_TEST); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); diff --git a/src/aquarium-direct-map/Program.cpp b/src/aquarium-direct-map/Program.cpp index 6ac3598..3f77a42 100644 --- a/src/aquarium-direct-map/Program.cpp +++ b/src/aquarium-direct-map/Program.cpp @@ -18,8 +18,9 @@ #include "build/build_config.h" #include "common/AQUARIUM_ASSERT.h" +#include "common/Path.h" -Program::Program(const std::string &vId, const std::string &fId) +Program::Program(Path &vId, Path &fId) : program(0u), attribLocs(), uniforms(), textureUnits() { createProgramFromTags(vId, fId); createSetters(); @@ -36,8 +37,7 @@ Program::~Program() { } } -void Program::createProgramFromTags(const std::string &vId, - const std::string &fId) { +void Program::createProgramFromTags(Path &vId, Path &fId) { std::ifstream VertexShaderStream(vId, std::ios::in); std::string VertexShaderCode( (std::istreambuf_iterator(VertexShaderStream)), diff --git a/src/aquarium-direct-map/Program.h b/src/aquarium-direct-map/Program.h index 5033871..6d7ddd2 100644 --- a/src/aquarium-direct-map/Program.h +++ b/src/aquarium-direct-map/Program.h @@ -16,9 +16,11 @@ #include "Texture.h" #include "Uniform.h" +class Path; + class Program { public: - Program(const std::string &vId, const std::string &fId); + Program(Path &vId, Path &fId); ~Program(); void use(); void setUniform(const std::string &name, float v); @@ -39,7 +41,7 @@ class Program { GLuint getProgramId() { return program; } private: - void createProgramFromTags(const std::string &vId, const std::string &fId); + void createProgramFromTags(Path &vId, Path &fId); GLuint LoadProgram(const std::string &vertexShader, const std::string &fragmentShader); void createSetters(); diff --git a/src/aquarium-direct-map/Scene.cpp b/src/aquarium-direct-map/Scene.cpp index f8ca31a..cd6d410 100644 --- a/src/aquarium-direct-map/Scene.cpp +++ b/src/aquarium-direct-map/Scene.cpp @@ -12,7 +12,6 @@ #include #include #include -#include #include "rapidjson/document.h" #include "rapidjson/filereadstream.h" @@ -23,14 +22,15 @@ #include "Model.h" #include "Program.h" #include "common/AQUARIUM_ASSERT.h" +#include "common/Path.h" -std::vector g_skyBoxUrls = { - "GlobeOuter_EM_positive_x.jpg", "GlobeOuter_EM_negative_x.jpg", - "GlobeOuter_EM_positive_y.jpg", "GlobeOuter_EM_negative_y.jpg", - "GlobeOuter_EM_positive_z.jpg", "GlobeOuter_EM_negative_z.jpg"}; +std::vector g_skyBoxUrls = { + Path("GlobeOuter_EM_positive_x.jpg"), Path("GlobeOuter_EM_negative_x.jpg"), + Path("GlobeOuter_EM_positive_y.jpg"), Path("GlobeOuter_EM_negative_y.jpg"), + Path("GlobeOuter_EM_positive_z.jpg"), Path("GlobeOuter_EM_negative_z.jpg")}; Scene::Scene(const std::string opt_programIds[2]) - : url(), models(), textureMap(), arrayMap() { + : url(Path::getVoidPath()), models(), textureMap(), arrayMap() { programIds[0] = opt_programIds[0]; programIds[1] = opt_programIds[1]; } @@ -65,26 +65,18 @@ Scene::~Scene() { } } -void Scene::setupSkybox(const std::string &path) { +void Scene::setupSkybox(const Path &path) { for (auto &v : g_skyBoxUrls) { - std::ostringstream url; - url << path << resourceFolder << slash << v; - - v = url.str(); + v = Path(path).push(resourceFolder).push(v); } } -void Scene::load(const std::string &path, const std::string &name) { - std::ostringstream oss; - oss << path << resourceFolder << slash; - std::string imagePath = oss.str(); - oss << name << ".js"; - std::string modelPath = oss.str(); - oss.str(""); - oss << path << shaderFolder << slash << shaderVersion << slash; - std::string programPath = oss.str(); - - this->url = modelPath; +void Scene::load(const Path &path, const std::string &name) { + Path imagePath = Path(path).push(resourceFolder); + Path modelPath = Path(imagePath).push(name + ".js"); + Path programPath = Path(path).push(shaderFolder).push(shaderVersion); + + this->url.push(modelPath); this->loaded = true; std::ifstream PlacementStream(modelPath, std::ios::in); @@ -102,7 +94,7 @@ void Scene::load(const std::string &path, const std::string &name) { std::string image = itr->value.GetString(); if (g_textureMap.find(image) == g_textureMap.end()) { - g_textureMap[image] = new Texture(imagePath + image, true); + g_textureMap[image] = new Texture(Path(imagePath).push(image), true); } textureMap[name] = g_textureMap[image]; @@ -143,7 +135,8 @@ void Scene::load(const std::string &path, const std::string &name) { std::string fsId; if (textureMap.find("diffuse") == textureMap.end()) { - std::cout << "missing diffuse texture for" << url.c_str() << std::endl; + std::cout << "missing diffuse texture for" << std::string(url) + << std::endl; } if (g_textureMap.find("skybox") == g_textureMap.end()) { @@ -159,7 +152,7 @@ void Scene::load(const std::string &path, const std::string &name) { textureMap["skybox"] = g_textureMap["skybox"]; } else if (textureMap.find("reflectionMap") != textureMap.end()) { if (textureMap.find("normalMap") != textureMap.end()) { - std::cout << "missing normal Map for" << url.c_str() << std::endl; + std::cout << "missing normal Map for" << std::string(url) << std::endl; } type = "reflection"; @@ -181,7 +174,8 @@ void Scene::load(const std::string &path, const std::string &name) { if (g_programMap.find(vsId + fsId) != g_programMap.end()) { program = g_programMap[vsId + fsId]; } else { - program = new Program(programPath + vsId, programPath + fsId); + program = new Program(Path(programPath).push(vsId), + Path(programPath).push(fsId)); g_programMap[vsId + fsId] = program; } diff --git a/src/aquarium-direct-map/Scene.h b/src/aquarium-direct-map/Scene.h index 8bb2ebf..1cdb409 100644 --- a/src/aquarium-direct-map/Scene.h +++ b/src/aquarium-direct-map/Scene.h @@ -14,6 +14,7 @@ #include "AttribBuffer.h" #include "Texture.h" +#include "common/Path.h" class Model; @@ -22,16 +23,16 @@ class Scene { ~Scene(); Scene(const std::string opt_programIds[2]); - void load(const std::string &path, const std::string &name); + void load(const Path &path, const std::string &name); const std::vector &getModels() const { return models; } bool loaded; private: - void setupSkybox(const std::string &path); + void setupSkybox(const Path &path); std::string programIds[2]; - std::string url; + Path url; std::vector models; std::unordered_map textureMap; std::unordered_map arrayMap; diff --git a/src/aquarium-direct-map/Texture.cpp b/src/aquarium-direct-map/Texture.cpp index 7d8e7e5..7a09b0f 100644 --- a/src/aquarium-direct-map/Texture.cpp +++ b/src/aquarium-direct-map/Texture.cpp @@ -9,14 +9,16 @@ #include "Texture.h" #include +#include #define STB_IMAGE_IMPLEMENTATION #include "stb_image.h" #include "common/AQUARIUM_ASSERT.h" +#include "common/Path.h" // initializs texture 2d -Texture::Texture(const std::string &url, bool flip) +Texture::Texture(const Path &url, bool flip) : urls(), target(GL_TEXTURE_2D), texture(0u), @@ -24,15 +26,14 @@ Texture::Texture(const std::string &url, bool flip) width(0), height(0), flip(flip) { - std::string urlpath = url; - urls.push_back(urlpath); + urls.push_back(url); glGenTextures(1, &texture); uploadTextures(); } // initializs cube map -Texture::Texture(const std::vector &urls) : urls(urls) { +Texture::Texture(const std::vector &urls) : urls(urls) { ASSERT(urls.size() == 6); target = GL_TEXTURE_CUBE_MAP; glGenTextures(1, &texture); @@ -58,11 +59,12 @@ bool Texture::isPowerOf2(int value) { // support 3 channel formats currently. The group is discussing on whether // webgpu shoud support 3 channel format. // https://github.com/gpuweb/gpuweb/issues/66#issuecomment-410021505 -bool Texture::loadImageBySTB(const std::string &filename, uint8_t **pixels) { +bool Texture::loadImageBySTB(Path &filename, uint8_t **pixels) { stbi_set_flip_vertically_on_load(flip); - *pixels = stbi_load(filename.c_str(), &width, &height, 0, 4); + *pixels = stbi_load(std::string(filename).c_str(), &width, &height, 0, 4); if (*pixels == 0) { - std::cout << stderr << "Couldn't open input file" << filename << std::endl; + std::cout << stderr << "Couldn't open input file" << std::string(filename) + << std::endl; return false; } return true; diff --git a/src/aquarium-direct-map/Texture.h b/src/aquarium-direct-map/Texture.h index 1002ad1..fdb6bbb 100644 --- a/src/aquarium-direct-map/Texture.h +++ b/src/aquarium-direct-map/Texture.h @@ -8,22 +8,23 @@ #ifndef TEXTURE_H #define TEXTURE_H -#include #include #include #include "glad/glad.h" +#include "common/Path.h" + class Texture { public: ~Texture(); - Texture(const std::string &url, bool flip); - Texture(const std::vector &urls); + Texture(const Path &url, bool flip); + Texture(const std::vector &urls); GLuint getTexture() const { return texture; } GLenum getTarget() const { return target; } void setTexture(GLuint texId) { texture = texId; } - bool loadImageBySTB(const std::string &filename, uint8_t **pixels); + bool loadImageBySTB(Path &filename, uint8_t **pixels); void DestroyImageData(uint8_t *pixels); private: @@ -31,7 +32,7 @@ class Texture { void uploadTextures(); bool isPowerOf2(int value); - std::vector urls; + std::vector urls; GLenum target; GLuint texture; std::unordered_map params; diff --git a/src/aquarium-optimized/Aquarium.cpp b/src/aquarium-optimized/Aquarium.cpp index a6d5977..3151dc0 100644 --- a/src/aquarium-optimized/Aquarium.cpp +++ b/src/aquarium-optimized/Aquarium.cpp @@ -31,6 +31,7 @@ #include "SeaweedModel.h" #include "Texture.h" #include "common/AQUARIUM_ASSERT.h" +#include "common/Path.h" #include "opengl/ContextGL.h" #if defined(OS_WIN) @@ -380,7 +381,7 @@ bool Aquarium::init(int argc, char **argv) { getElapsedTime(); const ResourceHelper *resourceHelper = mContext->getResourceHelper(); - std::vector skyUrls; + std::vector skyUrls; resourceHelper->getSkyBoxUrls(&skyUrls); mTextureMap["skybox"] = mContext->createTexture("skybox", skyUrls); @@ -450,7 +451,7 @@ void Aquarium::setupModelEnumMap() { // Load world matrices of models from json file. void Aquarium::loadPlacement() { const ResourceHelper *resourceHelper = mContext->getResourceHelper(); - std::string proppath = resourceHelper->getPropPlacementPath(); + Path proppath = resourceHelper->getPropPlacementPath(); std::ifstream PlacementStream(proppath, std::ios::in); rapidjson::IStreamWrapper isPlacement(PlacementStream); rapidjson::Document document; @@ -492,7 +493,7 @@ void Aquarium::loadModels() { void Aquarium::loadFishScenario() { const ResourceHelper *resourceHelper = mContext->getResourceHelper(); - std::string fishBehaviorPath = resourceHelper->getFishBehaviorPath(); + Path fishBehaviorPath = resourceHelper->getFishBehaviorPath(); std::ifstream FishStream(fishBehaviorPath, std::ios::in); rapidjson::IStreamWrapper is(FishStream); @@ -515,10 +516,9 @@ void Aquarium::loadFishScenario() { // Load vertex and index buffers, textures and program for each model. void Aquarium::loadModel(const G_sceneInfo &info) { const ResourceHelper *resourceHelper = mContext->getResourceHelper(); - std::string imagePath = resourceHelper->getImagePath(); - std::string programPath = resourceHelper->getProgramPath(); - std::string modelPath = - resourceHelper->getModelPath(std::string(info.namestr)); + Path imagePath = resourceHelper->getImagePath(); + Path programPath = resourceHelper->getProgramPath(); + Path modelPath = resourceHelper->getModelPath(std::string(info.namestr)); std::ifstream ModelStream(modelPath, std::ios::in); rapidjson::IStreamWrapper is(ModelStream); @@ -547,7 +547,8 @@ void Aquarium::loadModel(const G_sceneInfo &info) { std::string image = itr->value.GetString(); if (mTextureMap.find(image) == mTextureMap.end()) { - mTextureMap[image] = mContext->createTexture(name, imagePath + image); + mTextureMap[image] = + mContext->createTexture(name, Path(imagePath).push(image)); } model->textureMap[name] = mTextureMap[image]; @@ -608,7 +609,8 @@ void Aquarium::loadModel(const G_sceneInfo &info) { if (mProgramMap.find(vsId + fsId) != mProgramMap.end()) { program = mProgramMap[vsId + fsId]; } else { - program = mContext->createProgram(programPath + vsId, programPath + fsId); + program = mContext->createProgram(Path(programPath).push(vsId), + Path(programPath).push(fsId)); if (toggleBitset.test(static_cast(TOGGLE::ENABLEALPHABLENDING)) && info.type != MODELGROUP::INNER && info.type != MODELGROUP::OUTSIDE) { program->compileProgram(true, g.alpha); diff --git a/src/aquarium-optimized/Context.h b/src/aquarium-optimized/Context.h index 00fe635..3ed9b81 100644 --- a/src/aquarium-optimized/Context.h +++ b/src/aquarium-optimized/Context.h @@ -19,6 +19,7 @@ class Aquarium; class Buffer; class Model; +class Path; class Program; class Texture; @@ -36,18 +37,16 @@ class Context { const std::bitset(TOGGLE::TOGGLEMAX)> &toggleBitset, int windowWidth, int windowHeight) = 0; + virtual Texture *createTexture(const std::string &name, const Path &url) = 0; virtual Texture *createTexture(const std::string &name, - const std::string &url) = 0; - virtual Texture *createTexture(const std::string &name, - const std::vector &urls) = 0; + const std::vector &urls) = 0; virtual Buffer *createBuffer(int numComponents, std::vector *buffer, bool isIndex) = 0; virtual Buffer *createBuffer(int numComponents, std::vector *buffer, bool isIndex) = 0; - virtual Program *createProgram(const std::string &mVId, - const std::string &mFId) = 0; + virtual Program *createProgram(const Path &mVId, const Path &mFId) = 0; virtual void setWindowTitle(const std::string &text) = 0; virtual bool ShouldQuit() = 0; virtual void KeyBoardQuit() = 0; diff --git a/src/aquarium-optimized/Program.h b/src/aquarium-optimized/Program.h index 4b5d143..c73a865 100644 --- a/src/aquarium-optimized/Program.h +++ b/src/aquarium-optimized/Program.h @@ -10,9 +10,11 @@ #include +#include "common/Path.h" + class Program { public: - Program(const std::string &mVertexShader, const std::string &fragmentShader) + Program(const Path &mVertexShader, const Path &fragmentShader) : mVId(mVertexShader), mFId(fragmentShader) {} virtual ~Program() {} virtual void setProgram() {} @@ -22,8 +24,8 @@ class Program { protected: void loadProgram(); - std::string mVId; - std::string mFId; + Path mVId; + Path mFId; std::string VertexShaderCode; std::string FragmentShaderCode; diff --git a/src/aquarium-optimized/ResourceHelper.cpp b/src/aquarium-optimized/ResourceHelper.cpp index 6e05d80..cbc56c3 100644 --- a/src/aquarium-optimized/ResourceHelper.cpp +++ b/src/aquarium-optimized/ResourceHelper.cpp @@ -9,24 +9,8 @@ #include #include -#include "build/build_config.h" - #include "common/AQUARIUM_ASSERT.h" -#if defined(OS_WIN) -#include -#include -const std::string slash = "\\"; -#endif -#if defined(OS_MAC) -#include -const std::string slash = "/"; -#endif -#if defined(OS_LINUX) && !defined(OS_CHROMEOS) -#include -const std::string slash = "/"; -#endif - static const char *shaderFolder = "shaders"; static const char *resourceFolder = "assets"; @@ -38,49 +22,23 @@ const std::vector skyBoxUrls = { ResourceHelper::ResourceHelper(const std::string &mBackendName, const std::string &mShaderVersion, BACKENDTYPE backendType) - : mBackendName(mBackendName), + : mPath(Path::getVoidPath()), + mImagePath(Path::getVoidPath()), + mProgramPath(Path::getVoidPath()), + mPropPlacementPath(Path::getVoidPath()), + mModelPath(Path::getVoidPath()), + mFishBehaviorPath(Path::getVoidPath()), + mBackendName(mBackendName), mBackendType(backendType), mShaderVersion(mShaderVersion) { -#if defined(OS_WIN) - TCHAR temp[200]; - GetModuleFileName(nullptr, temp, MAX_PATH); - std::wstring ws(temp); - mPath = std::string(ws.begin(), ws.end()); -#elif defined(OS_MAC) - char temp[200]; - uint32_t size = sizeof(temp); - _NSGetExecutablePath(temp, &size); - mPath = std::string(temp); -#elif defined(OS_LINUX) && !defined(OS_CHROMEOS) - char temp[200]; - readlink("/proc/self/exe", temp, sizeof(temp)); - mPath = std::string(temp); -#else - ASSERT(false); -#endif - - size_t nPos = mPath.find_last_of(slash); - std::ostringstream pathStream; - pathStream << mPath.substr(0, nPos) << slash << ".." << slash << ".." - << slash; - mPath = pathStream.str(); - - std::ostringstream placementStream; - placementStream << mPath << resourceFolder << slash << "PropPlacement.js"; - mPropPlacementPath = placementStream.str(); - - std::ostringstream imageStream; - imageStream << mPath << resourceFolder << slash; - mImagePath = imageStream.str(); - - std::ostringstream programStream; - programStream << mPath << shaderFolder << slash << mBackendName << slash - << mShaderVersion << slash; - mProgramPath = programStream.str(); - - std::ostringstream fishBehaviorStream; - fishBehaviorStream << mPath << "FishBehavior.json"; - mFishBehaviorPath = fishBehaviorStream.str(); + mPath.push(Path::getExecutablePath()).pop().pop().pop(); + mPropPlacementPath.push(mPath).push(resourceFolder).push("PropPlacement.js"); + mImagePath.push(mPath).push(resourceFolder); + mProgramPath.push(mPath) + .push(shaderFolder) + .push(mBackendName) + .push(mShaderVersion); + mFishBehaviorPath.push(mPath).push("FishBehavior.json"); int expo = 0; while (1 << expo++ != BACKENDTYPE::BACKENDTYPENONE) { @@ -105,22 +63,18 @@ ResourceHelper::ResourceHelper(const std::string &mBackendName, } } -void ResourceHelper::getSkyBoxUrls(std::vector *skyUrls) const { +void ResourceHelper::getSkyBoxUrls(std::vector *skyUrls) const { for (auto &str : skyBoxUrls) { - std::ostringstream url; - url << mPath << resourceFolder << slash << str; - - skyUrls->emplace_back(url.str()); + Path url = Path(mPath).push(resourceFolder).push(str); + skyUrls->emplace_back(url); } } -std::string ResourceHelper::getModelPath(const std::string &modelName) const { - std::ostringstream modelStream; - modelStream << mImagePath << modelName << ".js"; - return modelStream.str(); +Path ResourceHelper::getModelPath(const std::string &modelName) const { + return Path(mImagePath).push(modelName + ".js"); } -const std::string &ResourceHelper::getProgramPath() const { +Path ResourceHelper::getProgramPath() const { return mProgramPath; } diff --git a/src/aquarium-optimized/ResourceHelper.h b/src/aquarium-optimized/ResourceHelper.h index 6a8fc42..d33c6ec 100644 --- a/src/aquarium-optimized/ResourceHelper.h +++ b/src/aquarium-optimized/ResourceHelper.h @@ -11,18 +11,19 @@ #include #include "Aquarium.h" +#include "common/Path.h" class ResourceHelper { public: ResourceHelper(const std::string &mBackendName, const std::string &mShaderVersion, BACKENDTYPE backendType); - void getSkyBoxUrls(std::vector *skyUrls) const; - const std::string &getPropPlacementPath() const { return mPropPlacementPath; } - const std::string &getImagePath() const { return mImagePath; } - std::string getModelPath(const std::string &modelName) const; - const std::string &getProgramPath() const; - const std::string &getFishBehaviorPath() const { return mFishBehaviorPath; } + void getSkyBoxUrls(std::vector *skyUrls) const; + Path getPropPlacementPath() const { return mPropPlacementPath; } + Path getImagePath() const { return mImagePath; } + Path getModelPath(const std::string &modelName) const; + Path getProgramPath() const; + Path getFishBehaviorPath() const { return mFishBehaviorPath; } const std::string &getBackendName() const { return mBackendName; } BACKENDTYPE getBackendType() const { return mBackendType; } const std::string &getShaderVersion() const { return mShaderVersion; } @@ -30,12 +31,12 @@ class ResourceHelper { void setRenderer(const std::string &renderer); private: - std::string mPath; - std::string mImagePath; - std::string mProgramPath; - std::string mPropPlacementPath; - std::string mModelPath; - std::string mFishBehaviorPath; + Path mPath; + Path mImagePath; + Path mProgramPath; + Path mPropPlacementPath; + Path mModelPath; + Path mFishBehaviorPath; std::string mBackendName; BACKENDTYPE mBackendType; diff --git a/src/aquarium-optimized/Texture.cpp b/src/aquarium-optimized/Texture.cpp index d857428..9ff4fcc 100644 --- a/src/aquarium-optimized/Texture.cpp +++ b/src/aquarium-optimized/Texture.cpp @@ -17,24 +17,25 @@ #include "stb_image_resize.h" #include "../common/AQUARIUM_ASSERT.h" +#include "../common/Path.h" -Texture::Texture(const std::string &name, const std::string &url, bool flip) +Texture::Texture(const std::string &name, const Path &url, bool flip) : mUrls(), mWidth(0), mHeight(0), mFlip(flip), mName(name) { - std::string urlpath = url; - mUrls.push_back(urlpath); + mUrls.push_back(url); } // Force loading 3 channel images to 4 channel by stb becasue Dawn doesn't // support 3 channel formats currently. The group is discussing on whether // webgpu shoud support 3 channel format. // https://github.com/gpuweb/gpuweb/issues/66#issuecomment-410021505 -bool Texture::loadImage(const std::vector &urls, +bool Texture::loadImage(const std::vector &urls, std::vector *pixels) { stbi_set_flip_vertically_on_load(mFlip); for (auto filename : urls) { - uint8_t *pixel = stbi_load(filename.c_str(), &mWidth, &mHeight, 0, 4); + uint8_t *pixel = + stbi_load(std::string(filename).c_str(), &mWidth, &mHeight, 0, 4); if (pixel == 0) { - std::cout << stderr << "Couldn't open input file" << filename + std::cout << stderr << "Couldn't open input file" << std::string(filename) << std::endl; return false; } diff --git a/src/aquarium-optimized/Texture.h b/src/aquarium-optimized/Texture.h index 40fadee..b00a12c 100644 --- a/src/aquarium-optimized/Texture.h +++ b/src/aquarium-optimized/Texture.h @@ -11,14 +11,14 @@ #include #include +#include "common/Path.h" + class Texture { public: virtual ~Texture() {} - Texture(const std::string &name, - const std::vector &urls, - bool flip) + Texture(const std::string &name, const std::vector &urls, bool flip) : mUrls(urls), mFlip(flip), mName(name) {} - Texture(const std::string &name, const std::string &url, bool flip); + Texture(const std::string &name, const Path &url, bool flip); std::string getName() { return mName; } virtual void loadTexture() = 0; void generateMipmap(uint8_t *input_pixels, @@ -34,8 +34,7 @@ class Texture { protected: bool isPowerOf2(int); - bool loadImage(const std::vector &urls, - std::vector *pixels); + bool loadImage(const std::vector &urls, std::vector *pixels); void DestoryImageData(std::vector &pixelVec); void copyPaddingBuffer(unsigned char *dst, unsigned char *src, @@ -43,7 +42,7 @@ class Texture { int height, int kPadding); - std::vector mUrls; + std::vector mUrls; int mWidth; int mHeight; bool mFlip; diff --git a/src/aquarium-optimized/d3d12/ContextD3D12.cpp b/src/aquarium-optimized/d3d12/ContextD3D12.cpp index f2e57dd..48fb51a 100644 --- a/src/aquarium-optimized/d3d12/ContextD3D12.cpp +++ b/src/aquarium-optimized/d3d12/ContextD3D12.cpp @@ -622,22 +622,20 @@ Buffer *ContextD3D12::createBuffer(int numComponents, return buffer; } -Program *ContextD3D12::createProgram(const std::string &mVId, - const std::string &mFId) { +Program *ContextD3D12::createProgram(const Path &mVId, const Path &mFId) { ProgramD3D12 *program = new ProgramD3D12(this, mVId, mFId); return program; } -Texture *ContextD3D12::createTexture(const std::string &name, - const std::string &url) { +Texture *ContextD3D12::createTexture(const std::string &name, const Path &url) { Texture *texture = new TextureD3D12(this, name, url); texture->loadTexture(); return texture; } Texture *ContextD3D12::createTexture(const std::string &name, - const std::vector &urls) { + const std::vector &urls) { Texture *texture = new TextureD3D12(this, name, urls); texture->loadTexture(); return texture; diff --git a/src/aquarium-optimized/d3d12/ContextD3D12.h b/src/aquarium-optimized/d3d12/ContextD3D12.h index 37c9947..dd17f93 100644 --- a/src/aquarium-optimized/d3d12/ContextD3D12.h +++ b/src/aquarium-optimized/d3d12/ContextD3D12.h @@ -58,13 +58,11 @@ class ContextD3D12 : public Context { std::vector *buffer, bool isIndex) override; - Program *createProgram(const std::string &mVId, - const std::string &mFId) override; + Program *createProgram(const Path &mVId, const Path &mFId) override; + Texture *createTexture(const std::string &name, const Path &url) override; Texture *createTexture(const std::string &name, - const std::string &url) override; - Texture *createTexture(const std::string &name, - const std::vector &urls) override; + const std::vector &urls) override; void initGeneralResources(Aquarium *aquarium) override; void updateWorldlUniforms(Aquarium *aquarium) override; diff --git a/src/aquarium-optimized/d3d12/ProgramD3D12.cpp b/src/aquarium-optimized/d3d12/ProgramD3D12.cpp index bfaf0f2..2bbe50e 100644 --- a/src/aquarium-optimized/d3d12/ProgramD3D12.cpp +++ b/src/aquarium-optimized/d3d12/ProgramD3D12.cpp @@ -12,8 +12,8 @@ #include "ContextD3D12.h" ProgramD3D12::ProgramD3D12(ContextD3D12 *context, - const std::string &mVId, - const std::string &mFId) + const Path &mVId, + const Path &mFId) : Program(mVId, mFId), mVertexShader(nullptr), mPixelShader(nullptr), diff --git a/src/aquarium-optimized/d3d12/ProgramD3D12.h b/src/aquarium-optimized/d3d12/ProgramD3D12.h index 59de716..f58c0b1 100644 --- a/src/aquarium-optimized/d3d12/ProgramD3D12.h +++ b/src/aquarium-optimized/d3d12/ProgramD3D12.h @@ -23,9 +23,7 @@ class ContextD3D12; class ProgramD3D12 : public Program { public: - ProgramD3D12(ContextD3D12 *context, - const std::string &mVId, - const std::string &mFId); + ProgramD3D12(ContextD3D12 *context, const Path &mVId, const Path &mFId); ~ProgramD3D12() override; void compileProgram(bool enableAlphaBlending, diff --git a/src/aquarium-optimized/d3d12/TextureD3D12.cpp b/src/aquarium-optimized/d3d12/TextureD3D12.cpp index 24f4901..ea8e2b3 100644 --- a/src/aquarium-optimized/d3d12/TextureD3D12.cpp +++ b/src/aquarium-optimized/d3d12/TextureD3D12.cpp @@ -16,7 +16,7 @@ TextureD3D12::~TextureD3D12() { TextureD3D12::TextureD3D12(ContextD3D12 *context, const std::string &name, - const std::string &url) + const Path &url) : Texture(name, url, true), mTextureDimension(D3D12_RESOURCE_DIMENSION_TEXTURE2D), mTextureViewDimension(D3D12_SRV_DIMENSION_TEXTURE2D), @@ -27,7 +27,7 @@ TextureD3D12::TextureD3D12(ContextD3D12 *context, TextureD3D12::TextureD3D12(ContextD3D12 *context, const std::string &name, - const std::vector &urls) + const std::vector &urls) : Texture(name, urls, false), mTextureDimension(D3D12_RESOURCE_DIMENSION_TEXTURE2D), mTextureViewDimension(D3D12_SRV_DIMENSION_TEXTURECUBE), diff --git a/src/aquarium-optimized/d3d12/TextureD3D12.h b/src/aquarium-optimized/d3d12/TextureD3D12.h index e80c736..5292856 100644 --- a/src/aquarium-optimized/d3d12/TextureD3D12.h +++ b/src/aquarium-optimized/d3d12/TextureD3D12.h @@ -21,12 +21,10 @@ class ContextD3D12; class TextureD3D12 : public Texture { public: ~TextureD3D12() override; + TextureD3D12(ContextD3D12 *context, const std::string &name, const Path &url); TextureD3D12(ContextD3D12 *context, const std::string &name, - const std::string &url); - TextureD3D12(ContextD3D12 *context, - const std::string &name, - const std::vector &urls); + const std::vector &urls); D3D12_RESOURCE_DIMENSION getTextureDimension() { return mTextureDimension; } D3D12_SRV_DIMENSION getTextureViewDimension() { diff --git a/src/aquarium-optimized/dawn/ContextDawn.cpp b/src/aquarium-optimized/dawn/ContextDawn.cpp index 870bfa7..6b5feb8 100644 --- a/src/aquarium-optimized/dawn/ContextDawn.cpp +++ b/src/aquarium-optimized/dawn/ContextDawn.cpp @@ -340,15 +340,14 @@ void ContextDawn::initAvailableToggleBitset(BACKENDTYPE backendType) { mAvailableToggleBitset.set(static_cast(TOGGLE::DRAWPERMODEL)); } -Texture *ContextDawn::createTexture(const std::string &name, - const std::string &url) { +Texture *ContextDawn::createTexture(const std::string &name, const Path &url) { Texture *texture = new TextureDawn(this, name, url); texture->loadTexture(); return texture; } Texture *ContextDawn::createTexture(const std::string &name, - const std::vector &urls) { + const std::vector &urls) { Texture *texture = new TextureDawn(this, name, urls); texture->loadTexture(); return texture; @@ -851,8 +850,7 @@ Buffer *ContextDawn::createBuffer(int numComponents, return buffer; } -Program *ContextDawn::createProgram(const std::string &mVId, - const std::string &mFId) { +Program *ContextDawn::createProgram(const Path &mVId, const Path &mFId) { ProgramDawn *program = new ProgramDawn(this, mVId, mFId); return program; diff --git a/src/aquarium-optimized/dawn/ContextDawn.h b/src/aquarium-optimized/dawn/ContextDawn.h index 1e7ee71..24d5c41 100644 --- a/src/aquarium-optimized/dawn/ContextDawn.h +++ b/src/aquarium-optimized/dawn/ContextDawn.h @@ -83,13 +83,11 @@ class ContextDawn : public Context { std::vector *buffer, bool isIndex) override; - Program *createProgram(const std::string &mVId, - const std::string &mFId) override; + Program *createProgram(const Path &mVId, const Path &mFId) override; + Texture *createTexture(const std::string &name, const Path &url) override; Texture *createTexture(const std::string &name, - const std::string &url) override; - Texture *createTexture(const std::string &name, - const std::vector &urls) override; + const std::vector &urls) override; wgpu::Texture createTexture(const wgpu::TextureDescriptor &descriptor) const; wgpu::Sampler createSampler(const wgpu::SamplerDescriptor &descriptor) const; wgpu::Buffer createBufferFromData(const void *data, diff --git a/src/aquarium-optimized/dawn/ProgramDawn.cpp b/src/aquarium-optimized/dawn/ProgramDawn.cpp index 5aa5669..69fe1b4 100644 --- a/src/aquarium-optimized/dawn/ProgramDawn.cpp +++ b/src/aquarium-optimized/dawn/ProgramDawn.cpp @@ -14,8 +14,8 @@ #include "common/AQUARIUM_ASSERT.h" ProgramDawn::ProgramDawn(ContextDawn *context, - const std::string &mVId, - const std::string &mFId) + const Path &mVId, + const Path &mFId) : Program(mVId, mFId), mVsModule(nullptr), mFsModule(nullptr), diff --git a/src/aquarium-optimized/dawn/ProgramDawn.h b/src/aquarium-optimized/dawn/ProgramDawn.h index e3e2fd5..527841b 100644 --- a/src/aquarium-optimized/dawn/ProgramDawn.h +++ b/src/aquarium-optimized/dawn/ProgramDawn.h @@ -21,9 +21,7 @@ class ContextDawn; class ProgramDawn : public Program { public: - ProgramDawn(ContextDawn *context, - const std::string &mVId, - const std::string &mFId); + ProgramDawn(ContextDawn *context, const Path &mVId, const Path &mFId); ~ProgramDawn() override; void compileProgram(bool enableAlphaBlending, diff --git a/src/aquarium-optimized/dawn/TextureDawn.cpp b/src/aquarium-optimized/dawn/TextureDawn.cpp index f0fbeea..ba0a05b 100644 --- a/src/aquarium-optimized/dawn/TextureDawn.cpp +++ b/src/aquarium-optimized/dawn/TextureDawn.cpp @@ -25,7 +25,7 @@ TextureDawn::~TextureDawn() { TextureDawn::TextureDawn(ContextDawn *context, const std::string &name, - const std::string &url) + const Path &url) : Texture(name, url, true), mTextureDimension(wgpu::TextureDimension::e2D), mTextureViewDimension(wgpu::TextureViewDimension::e2D), @@ -38,7 +38,7 @@ TextureDawn::TextureDawn(ContextDawn *context, TextureDawn::TextureDawn(ContextDawn *context, const std::string &name, - const std::vector &urls) + const std::vector &urls) : Texture(name, urls, false), mTextureDimension(wgpu::TextureDimension::e2D), mTextureViewDimension(wgpu::TextureViewDimension::Cube), diff --git a/src/aquarium-optimized/dawn/TextureDawn.h b/src/aquarium-optimized/dawn/TextureDawn.h index d4b4611..5422961 100644 --- a/src/aquarium-optimized/dawn/TextureDawn.h +++ b/src/aquarium-optimized/dawn/TextureDawn.h @@ -17,12 +17,10 @@ class ContextDawn; class TextureDawn : public Texture { public: ~TextureDawn() override; + TextureDawn(ContextDawn *context, const std::string &name, const Path &url); TextureDawn(ContextDawn *context, const std::string &name, - const std::string &url); - TextureDawn(ContextDawn *context, - const std::string &name, - const std::vector &urls); + const std::vector &urls); const wgpu::Texture &getTextureId() const { return mTexture; } const wgpu::Sampler &getSampler() const { return mSampler; } diff --git a/src/aquarium-optimized/dawn/imgui_impl_dawn.cpp b/src/aquarium-optimized/dawn/imgui_impl_dawn.cpp index e7fc5b9..cd1e534 100644 --- a/src/aquarium-optimized/dawn/imgui_impl_dawn.cpp +++ b/src/aquarium-optimized/dawn/imgui_impl_dawn.cpp @@ -4,6 +4,7 @@ #include #include "ProgramDawn.h" +#include "common/Path.h" #include "imgui.h" #include "imgui_impl_dawn.h" @@ -289,9 +290,9 @@ bool ImGui_ImplDawn_CreateDeviceObjects(int MSAASampleCount, bool enableAlphaBle wgpu::PipelineLayout mPipelineLayout = mContextDawn->MakeBasicPipelineLayout({layout}); ResourceHelper *resourceHelper = mContextDawn->getResourceHelper(); - std::string programPath = resourceHelper->getProgramPath(); - mProgramDawn = new ProgramDawn(mContextDawn, programPath + "imguiVertexShader", - programPath + "imguiFragmentShader"); + Path programPath = resourceHelper->getProgramPath(); + mProgramDawn = new ProgramDawn(mContextDawn, Path(programPath).push("imguiVertexShader"), + Path(programPath).push("imguiFragmentShader")); mProgramDawn->compileProgram(false, ""); wgpu::StencilStateFaceDescriptor stencilStateFaceDescriptor; diff --git a/src/aquarium-optimized/opengl/ContextGL.cpp b/src/aquarium-optimized/opengl/ContextGL.cpp index 8e220f7..16c60dd 100644 --- a/src/aquarium-optimized/opengl/ContextGL.cpp +++ b/src/aquarium-optimized/opengl/ContextGL.cpp @@ -333,15 +333,14 @@ void ContextGL::framebufferResizeCallback(GLFWwindow *window, glViewport(0, 0, width, height); } -Texture *ContextGL::createTexture(const std::string &name, - const std::string &url) { +Texture *ContextGL::createTexture(const std::string &name, const Path &url) { TextureGL *texture = new TextureGL(this, name, url); texture->loadTexture(); return texture; } Texture *ContextGL::createTexture(const std::string &name, - const std::vector &urls) { + const std::vector &urls) { TextureGL *texture = new TextureGL(this, name, urls); texture->loadTexture(); return texture; @@ -416,8 +415,7 @@ Buffer *ContextGL::createBuffer(int numComponents, return buffer; } -Program *ContextGL::createProgram(const std::string &mVId, - const std::string &mFId) { +Program *ContextGL::createProgram(const Path &mVId, const Path &mFId) { ProgramGL *program = new ProgramGL(this, mVId, mFId); return program; diff --git a/src/aquarium-optimized/opengl/ContextGL.h b/src/aquarium-optimized/opengl/ContextGL.h index 51d79dc..2b093c5 100644 --- a/src/aquarium-optimized/opengl/ContextGL.h +++ b/src/aquarium-optimized/opengl/ContextGL.h @@ -83,8 +83,7 @@ class ContextGL : public Context { void uploadBuffer(unsigned int target, const std::vector &buf); - Program *createProgram(const std::string &mVId, - const std::string &mFId) override; + Program *createProgram(const Path &mVId, const Path &mFId) override; unsigned int generateProgram(); void setProgram(unsigned int program); void deleteProgram(unsigned int program); @@ -95,10 +94,9 @@ class ContextGL : public Context { unsigned int generateVAO(); void deleteVAO(unsigned int vao); + Texture *createTexture(const std::string &name, const Path &url) override; Texture *createTexture(const std::string &name, - const std::string &url) override; - Texture *createTexture(const std::string &name, - const std::vector &urls) override; + const std::vector &urls) override; unsigned int generateTexture(); void bindTexture(unsigned int target, unsigned int texture); void deleteTexture(unsigned int texture); diff --git a/src/aquarium-optimized/opengl/ProgramGL.cpp b/src/aquarium-optimized/opengl/ProgramGL.cpp index 4d7d4e8..bb77184 100644 --- a/src/aquarium-optimized/opengl/ProgramGL.cpp +++ b/src/aquarium-optimized/opengl/ProgramGL.cpp @@ -34,7 +34,7 @@ #include "../Texture.h" #include "common/AQUARIUM_ASSERT.h" -ProgramGL::ProgramGL(ContextGL *context, std::string mVId, std::string mFId) +ProgramGL::ProgramGL(ContextGL *context, const Path &mVId, const Path &mFId) : Program(mVId, mFId), mProgramId(0u), mContext(context) { mProgramId = context->generateProgram(); mVAO = context->generateVAO(); diff --git a/src/aquarium-optimized/opengl/ProgramGL.h b/src/aquarium-optimized/opengl/ProgramGL.h index ad4ae1c..97e6f51 100644 --- a/src/aquarium-optimized/opengl/ProgramGL.h +++ b/src/aquarium-optimized/opengl/ProgramGL.h @@ -22,7 +22,7 @@ class ProgramGL : public Program { public: - ProgramGL(ContextGL *, std::string mVId, std::string mFId); + ProgramGL(ContextGL *, const Path &mVId, const Path &mFId); ~ProgramGL() override; void setProgram() override; diff --git a/src/aquarium-optimized/opengl/TextureGL.cpp b/src/aquarium-optimized/opengl/TextureGL.cpp index 4e2ebfa..e4ec95a 100644 --- a/src/aquarium-optimized/opengl/TextureGL.cpp +++ b/src/aquarium-optimized/opengl/TextureGL.cpp @@ -11,7 +11,7 @@ #include "common/AQUARIUM_ASSERT.h" // initializs texture 2d -TextureGL::TextureGL(ContextGL *context, std::string name, std::string url) +TextureGL::TextureGL(ContextGL *context, std::string name, const Path &url) : Texture(name, url, true), mTarget(GL_TEXTURE_2D), mFormat(GL_RGBA), @@ -22,7 +22,7 @@ TextureGL::TextureGL(ContextGL *context, std::string name, std::string url) // initializs cube map TextureGL::TextureGL(ContextGL *context, std::string name, - const std::vector &urls) + const std::vector &urls) : Texture(name, urls, false), mTarget(GL_TEXTURE_CUBE_MAP), mFormat(GL_RGBA), diff --git a/src/aquarium-optimized/opengl/TextureGL.h b/src/aquarium-optimized/opengl/TextureGL.h index d294601..2066a1b 100644 --- a/src/aquarium-optimized/opengl/TextureGL.h +++ b/src/aquarium-optimized/opengl/TextureGL.h @@ -32,10 +32,10 @@ class ContextGL; class TextureGL : public Texture { public: ~TextureGL() override; - TextureGL(ContextGL *context, std::string name, std::string url); + TextureGL(ContextGL *context, std::string name, const Path &url); TextureGL(ContextGL *context, std::string name, - const std::vector &urls); + const std::vector &urls); unsigned int getTextureId() const { return mTextureId; } unsigned int getTarget() const { return mTarget; } diff --git a/src/common/Path.cpp b/src/common/Path.cpp new file mode 100644 index 0000000..7e3d304 --- /dev/null +++ b/src/common/Path.cpp @@ -0,0 +1,373 @@ +// +// Copyright (c) 2021 The Aquarium Project Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// + +#include "Path.h" + +#include + +#include "build/build_config.h" + +#include "AQUARIUM_ASSERT.h" + +#if defined(OS_WIN) +#include +#include +#include +#endif +#if defined(OS_MAC) +#include +#include +#endif +#if defined(OS_LINUX) && !defined(OS_CHROMEOS) +#include +#include +#include +#endif + +const std::string Path::kCurrentDirectory = "."; +const std::string Path::kParentDirectory = ".."; +#if defined(OS_WIN) +const char Path::kSeparator = '\\'; +const std::vector Path::kAlternativeSeparators = {'/'}; +#endif +#if defined(OS_MAC) || (defined(OS_LINUX) && !defined(OS_CHROMEOS)) +const char Path::kSeparator = '/'; +const std::vector Path::kAlternativeSeparators = {}; +#endif + +Path::Path(const std::string &path) { + auto begin = path.cbegin(); + auto end = path.cbegin(); + + // Parse root name +#if defined(OS_WIN) + if (end != path.cend() && + std::isalpha(static_cast(*end))) { // DOS path + ++end; + if (end != path.cend() && *end == ':') { + ++end; + mRootName = std::string(begin, end); + begin = end; + } + } else if (end != path.cend() && isSeparator(*end)) { // UNC path or DOS + // device path + ++end; + if (end != path.cend() && isSeparator(*end)) { + ++end; + if (end != path.cend() && !isSeparator(*end)) { + ++end; + while (end != path.cend() && !isSeparator(*end)) { + ++end; + } + mRootName = + std::string({kSeparator, kSeparator}) + std::string(begin + 2, end); + begin = end; + } + } + } + end = begin; +#endif + + // Split the remaining by directory separator(s) + // N.B. there is an empty filename before the leading or after the trailing + // separator. + while (begin != path.cend()) { + begin = end; + while (end != path.cend() && !isSeparator(*end)) { + ++end; + } + mFilenames.emplace_back(begin, end); + begin = end; + while (end != path.cend() && isSeparator(*end)) { + ++end; + } + } + + normalize(); +} + +Path::Path() { +} + +Path::~Path() { +} + +Path Path::getVoidPath() { + return Path(); +} + +Path Path::getExecutablePath() { + std::string path; +#if defined(OS_WIN) + std::vector tstr; + std::vector str; + size_t size; + tstr.resize(MAX_PATH); // start from a reasonable buffer size + do { + tstr.push_back({}); // increment the buffer size if it's not large enough + size = GetModuleFileName(nullptr, tstr.data(), tstr.size()); + } while (size == tstr.size()); + tstr.resize(size); +#if defined(UNICODE) || defined(_UNICODE) + size = WideCharToMultiByte(CP_UTF8, 0, tstr.data(), tstr.size(), nullptr, 0, + nullptr, nullptr); + str.resize(size); + size = WideCharToMultiByte(CP_UTF8, 0, tstr.data(), tstr.size(), str.data(), + str.size(), nullptr, nullptr); + path = std::string(str.data(), str.size()); +#else + path = std::string(tstr.data(), tstr.size()); +#endif +#elif defined(OS_MAC) + std::vector str; + uint32_t size = 0; + _NSGetExecutablePath(nullptr, &size); + str.resize(size); + _NSGetExecutablePath(str.data(), &size); + path = std::string(str.data()); +#elif defined(OS_LINUX) && !defined(OS_CHROMEOS) + const std::string link = "/proc/self/exe"; + std::vector str; + size_t size; + str.resize(PATH_MAX); // start from a reasonable buffer size + do { + str.push_back({}); // increment the buffer size if it's not large enough + size = readlink(link.c_str(), str.data(), str.size()); + } while (size == str.size()); + str.resize(size); + path = std::string(str.data(), str.size()); +#else + ASSERT(false); +#endif + return Path(path); +} + +bool Path::isSeparator(char ch) { + if (ch == kSeparator) { + return true; + } + for (auto separator : kAlternativeSeparators) { + if (ch == separator) { + return true; + } + } + return false; +} + +bool Path::isAbsolute() const { + auto ch = mRootName.cbegin(); +#if defined(OS_WIN) + if (ch != mRootName.cend() && std::isalpha(static_cast(*ch))) { + ++ch; + if (ch != mRootName.cend() && *ch == ':') { + ++ch; + return mFilenames.size() > 1 && mFilenames.front() == ""; + } + } else if (ch != mRootName.cend() && isSeparator(*ch)) { + ++ch; + if (ch != mRootName.cend() && isSeparator(*ch)) { + ++ch; + if (ch != mRootName.cend() && !isSeparator(*ch)) { + ++ch; + return true; + } + } + } +#elif defined(OS_MAC) || (defined(OS_LINUX) && !defined(OS_CHROMEOS)) + if (ch == mRootName.cend()) { + return mFilenames.size() > 1 && mFilenames.front() == ""; + } +#else + ASSERT(false); +#endif + return false; +} + +bool Path::isRelative() const { + auto ch = mRootName.cbegin(); +#if defined(OS_WIN) + if (ch == mRootName.cend()) { + // The special void path ("") is excluded, although C++17 views it as a + // relative path. + return !mFilenames.empty(); + } else if (std::isalpha(static_cast(*ch))) { + ++ch; + if (ch != mRootName.cend() && *ch == ':') { + ++ch; + return mFilenames.size() <= 1 || mFilenames.front() != ""; + } + } +#elif defined(OS_MAC) || (defined(OS_LINUX) && !defined(OS_CHROMEOS)) + if (ch == mRootName.cend()) { + // The special void path ("") is excluded. + if (!mFilenames.empty()) { + return mFilenames.size() == 1 || mFilenames.front() != ""; + } + } +#else + ASSERT(false); +#endif + return false; +} + +Path &Path::push(const Path &path) { + if (path.isAbsolute()) { + *this = path; + return *this; + } else { + if (path.mRootName != "" && path.mRootName != mRootName) { + *this = path; + return *this; + } + + if (path.mFilenames.size() > 1 && path.mFilenames.front() == "") { + mFilenames = path.mFilenames; + return *this; + } + + for (const auto &filename : path.mFilenames) { + if (!mFilenames.empty() && mFilenames.back() == "") { + mFilenames.pop_back(); // strip the trailing empty filename + } + mFilenames.push_back(filename); + } + return *this; + } +} + +Path &Path::pop() { + if (isAbsolute()) { + if (!mFilenames.empty() && mFilenames.back() == "") { + mFilenames.pop_back(); // strip the trailing empty filename first + } + if (mFilenames.empty() || mFilenames.back() == "") { + // Current root directory + mFilenames.push_back(""); + } else if (mFilenames.back() == kParentDirectory) { + mFilenames.push_back(kParentDirectory); + } else { + // Path ending with a normal filename + // No matter if this is the only remaining filename, the resulting + // absolute path should always have a trailing directory separator. + ASSERT(mFilenames.back() != kCurrentDirectory); + mFilenames.pop_back(); + mFilenames.push_back(""); + } + return *this; + } else { + if (!mFilenames.empty() && mFilenames.back() == "") { + mFilenames.pop_back(); // strip the trailing empty filename first + } + if (mFilenames.empty() || mFilenames.back() == kParentDirectory) { + mFilenames.push_back(kParentDirectory); + } else if (mFilenames.back() == "") { + // Current root directory + mFilenames.push_back(""); + } else { + // Path ending with a normal filename + // When popping the last remaining filename, a trailing empty filename + // helps to distinguish the internal representation of "." from that of + // "". + ASSERT(mFilenames.back() != kCurrentDirectory); + mFilenames.pop_back(); + mFilenames.push_back(""); + } + return *this; + } +} + +void Path::normalize() { + std::vector filenames; + if (isAbsolute()) { + for (auto it = mFilenames.cbegin(); it != mFilenames.cend(); ++it) { + if (*it == "") { + if (std::next(it) != mFilenames.cend()) { + // Current root directory + ASSERT(it == mFilenames.cbegin()); + filenames.push_back(*it); + } + } else if (*it == kParentDirectory) { + if (!filenames.empty() && filenames.back() != "") { + // Parent directory following a normal filename + ASSERT(filenames.back() != kCurrentDirectory); + ASSERT(filenames.back() != kParentDirectory); + filenames.pop_back(); + } + } else if (*it != kCurrentDirectory) { + // Normal filename + filenames.push_back(*it); + continue; + } + // Special filenames fall through here. A trailing directory separator is + // added on demand. + if (std::next(it) == mFilenames.cend()) { + ASSERT(filenames.empty() || filenames.back() != kCurrentDirectory); + ASSERT(filenames.empty() || filenames.back() != kParentDirectory); + filenames.push_back(""); + } + } + } else { + for (auto it = mFilenames.cbegin(); it != mFilenames.cend(); ++it) { + if (*it == "") { + if (std::next(it) != mFilenames.cend()) { + // Current root directory + ASSERT(it == mFilenames.cbegin()); + filenames.push_back(*it); + } + } else if (*it == kParentDirectory) { + if (filenames.empty() || filenames.back() == kParentDirectory) { + filenames.push_back(*it); + } else if (filenames.back() != "") { + // Parent directory following a normal filename + ASSERT(filenames.back() != kCurrentDirectory); + filenames.pop_back(); + } + } else if (*it != kCurrentDirectory) { + // Normal filename + filenames.push_back(*it); + continue; + } + // Special filenames fall through here. A trailing directory separator is + // conditionally added depending on current path ending. + if (std::next(it) == mFilenames.cend()) { + ASSERT(filenames.empty() || filenames.back() != kCurrentDirectory); + if (filenames.empty() || filenames.back() != kParentDirectory) { + filenames.push_back(""); + } + } + } + } + mFilenames = filenames; +} + +Path::operator std::string() { + normalize(); + + std::string path = mRootName; + if (isAbsolute()) { + for (auto it = mFilenames.cbegin(); it != mFilenames.cend(); ++it) { + if (std::next(it) == mFilenames.cend() || *it != "") { + // Ensure a directory separator will be inserted after the root name + // when necessary + path += kSeparator; + } + path += *it; + } + } else { + for (auto it = mFilenames.cbegin(); it != mFilenames.cend(); ++it) { + if (it != mFilenames.cbegin()) { + path += kSeparator; + } else if (std::next(it) == mFilenames.cend() && *it == "") { + // Prevent path to current directory from being converted to "" + if (path == "") { + path = kCurrentDirectory; + } + } + path += *it; + } + } + return path; +} diff --git a/src/common/Path.h b/src/common/Path.h new file mode 100644 index 0000000..16de130 --- /dev/null +++ b/src/common/Path.h @@ -0,0 +1,47 @@ +// +// Copyright (c) 2021 The Aquarium Project Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// + +#ifndef PATH_H +#define PATH_H + +#include +#include + +class Path { +public: + static Path getVoidPath(); + static Path getExecutablePath(); + + explicit Path(const std::string &path); + template + explicit Path(const T &path) : Path(std::string(path)) {} + ~Path(); + bool isAbsolute() const; + bool isRelative() const; + Path &push(const Path &path); + template + Path &push(const T &path) { + return push(Path(path)); + } + Path &pop(); + operator std::string(); + +private: + static bool isSeparator(char ch); + + static const std::string kCurrentDirectory; + static const std::string kParentDirectory; + static const char kSeparator; + static const std::vector kAlternativeSeparators; + + Path(); + void normalize(); + + std::string mRootName; + std::vector mFilenames; +}; + +#endif // PATH_H