Skip to main content

Overview

The Workspace system provides centralized asset management for Atlas Engine. It handles resource discovery, loading, and organization through a singleton interface that maintains resource metadata and groups.

Core Concepts

Resource

A Resource represents a single asset on disk:
struct Resource {
    fs::path path;        // Filesystem path to the asset
    std::string name;     // Human-readable identifier
    ResourceType type;    // Type classification
};

ResourceGroup

A ResourceGroup organizes related resources:
struct ResourceGroup {
    std::string groupName;           // Group identifier
    std::vector<Resource> resources; // Resources in this group
    
    Resource findResource(const std::string& name);
};

Getting Started

Accessing the Workspace

#include <atlas/workspace.h>

// Get the singleton instance
Workspace& workspace = Workspace::get();

// Set the root path for all relative paths
workspace.setRootPath("assets/");

Resource Types

The engine supports several built-in resource types:
enum class ResourceType {
    File,         // Generic file
    Image,        // Texture image (PNG, JPG, etc.)
    SpecularMap,  // Specular/roughness maps
    Audio,        // Sound files
    Font,         // Font files
    Model         // 3D model files
};

Creating Resources

Single Resource

// Create a texture resource
Resource brickTexture = workspace.createResource(
    "textures/brick.png",
    "BrickTexture",
    ResourceType::Image
);

// Create an audio resource
Resource footstep = workspace.createResource(
    "audio/footstep.wav",
    "FootstepSound",
    ResourceType::Audio
);

// Create a model resource
Resource character = workspace.createResource(
    "models/character.obj",
    "MainCharacter",
    ResourceType::Model
);
Resource creation handles:
  • Path resolution (relative to root path)
  • Duplicate detection (workspace.cpp:15-19)
  • Automatic logging via Tracer (workspace.cpp:21)

Path Resolution

The workspace automatically resolves relative paths:
// From workspace.cpp:23-28
if (rootPath && path.is_relative()) {
    res.path = rootPath.value() / path;
} else {
    res.path = path;
}

Retrieving Resources

By Name

Resource texture = workspace.getResource("BrickTexture");
if (texture.name.empty()) {
    // Resource not found (warning logged at workspace.cpp:52)
}

All Resources

std::vector<Resource> allResources = workspace.getAllResources();
for (const auto& res : allResources) {
    std::cout << res.name << ": " << res.path << std::endl;
}

By Type

// Get all textures
std::vector<Resource> textures = 
    workspace.getResourcesByType(ResourceType::Image);

// Get all audio files
std::vector<Resource> sounds = 
    workspace.getResourcesByType(ResourceType::Audio);

// Get all models
std::vector<Resource> models = 
    workspace.getResourcesByType(ResourceType::Model);
Implementation filters resources by type (workspace.cpp:58-66).

Resource Groups

Creating Groups

Group related assets together:
// Create skybox resources
std::vector<Resource> skyboxFaces = {
    workspace.createResource("skybox/right.jpg", "SkyboxRight", ResourceType::Image),
    workspace.createResource("skybox/left.jpg", "SkyboxLeft", ResourceType::Image),
    workspace.createResource("skybox/top.jpg", "SkyboxTop", ResourceType::Image),
    workspace.createResource("skybox/bottom.jpg", "SkyboxBottom", ResourceType::Image),
    workspace.createResource("skybox/front.jpg", "SkyboxFront", ResourceType::Image),
    workspace.createResource("skybox/back.jpg", "SkyboxBack", ResourceType::Image)
};

ResourceGroup skybox = workspace.createResourceGroup(
    "Skybox",
    skyboxFaces
);
Group creation is logged via Tracer (workspace.cpp:38).

Retrieving Groups

// Get a specific group
ResourceGroup skybox = workspace.getResourceGroup("Skybox");

// Get all groups
std::vector<ResourceGroup> allGroups = workspace.getAllResourceGroups();

Finding Resources in Groups

ResourceGroup skybox = workspace.getResourceGroup("Skybox");
Resource rightFace = skybox.findResource("SkyboxRight");

if (rightFace.name.empty()) {
    // Not found (error logged at workspace.cpp:88)
}
Implementation searches group resources (workspace.cpp:82-90).

Real-World Examples

Material System

void loadMaterial(const std::string& materialName) {
    Workspace& ws = Workspace::get();
    
    // Load material textures
    Resource albedo = ws.getResource(materialName + "_Albedo");
    Resource normal = ws.getResource(materialName + "_Normal");
    Resource roughness = ws.getResource(materialName + "_Roughness");
    Resource metallic = ws.getResource(materialName + "_Metallic");
    
    // Create material from resources
    Material mat;
    mat.albedoMap = loadTexture(albedo.path);
    mat.normalMap = loadTexture(normal.path);
    mat.roughnessMap = loadTexture(roughness.path);
    mat.metallicMap = loadTexture(metallic.path);
    
    return mat;
}

Level Loading

void loadLevel(const std::string& levelName) {
    Workspace& ws = Workspace::get();
    ws.setRootPath("levels/" + levelName + "/");
    
    // Load level geometry
    auto models = ws.getResourcesByType(ResourceType::Model);
    for (const auto& model : models) {
        loadModel(model.path);
    }
    
    // Load level textures
    auto textures = ws.getResourcesByType(ResourceType::Image);
    for (const auto& tex : textures) {
        loadTexture(tex.path);
    }
    
    // Load level audio
    auto sounds = ws.getResourcesByType(ResourceType::Audio);
    for (const auto& sound : sounds) {
        loadAudio(sound.path);
    }
}

Character Asset Bundle

ResourceGroup createCharacterBundle(const std::string& characterName) {
    Workspace& ws = Workspace::get();
    
    std::vector<Resource> characterAssets = {
        // Model
        ws.createResource(
            "characters/" + characterName + "/model.fbx",
            characterName + "_Model",
            ResourceType::Model
        ),
        // Textures
        ws.createResource(
            "characters/" + characterName + "/albedo.png",
            characterName + "_Albedo",
            ResourceType::Image
        ),
        ws.createResource(
            "characters/" + characterName + "/normal.png",
            characterName + "_Normal",
            ResourceType::Image
        ),
        // Audio
        ws.createResource(
            "characters/" + characterName + "/voice.wav",
            characterName + "_Voice",
            ResourceType::Audio
        )
    };
    
    return ws.createResourceGroup(
        characterName + "_Bundle",
        characterAssets
    );
}

Runtime Asset Discovery

void discoverAssets(const std::filesystem::path& directory) {
    Workspace& ws = Workspace::get();
    
    for (const auto& entry : std::filesystem::recursive_directory_iterator(directory)) {
        if (!entry.is_regular_file()) continue;
        
        std::string ext = entry.path().extension().string();
        ResourceType type = ResourceType::File;
        
        // Classify by extension
        if (ext == ".png" || ext == ".jpg" || ext == ".tga") {
            type = ResourceType::Image;
        } else if (ext == ".wav" || ext == ".ogg" || ext == ".mp3") {
            type = ResourceType::Audio;
        } else if (ext == ".obj" || ext == ".fbx" || ext == ".gltf") {
            type = ResourceType::Model;
        } else if (ext == ".ttf" || ext == ".otf") {
            type = ResourceType::Font;
        }
        
        ws.createResource(
            entry.path(),
            entry.path().stem().string(),
            type
        );
    }
}

Error Handling

The workspace system provides feedback through Tracer logging:
// Creating duplicate resources (workspace.cpp:16-18)
Resource res1 = workspace.createResource("tex.png", "MyTex", ResourceType::Image);
Resource res2 = workspace.createResource("tex.png", "MyTex", ResourceType::Image);
// Logs: "Resource already exists: MyTex" and returns existing resource

// Retrieving non-existent resources (workspace.cpp:52)
Resource missing = workspace.getResource("DoesNotExist");
// Logs: "Resource not found: DoesNotExist"
// Returns empty Resource with empty name

// Searching non-existent groups (workspace.cpp:78)
ResourceGroup missing = workspace.getResourceGroup("NoSuchGroup");
// Logs: "Resource group not found: NoSuchGroup"
// Returns empty ResourceGroup

// Finding missing resources in groups (workspace.cpp:88)
ResourceGroup group = workspace.getResourceGroup("MyGroup");
Resource missing = group.findResource("NotInGroup");
// Logs: "Resource not found in group: NotInGroup"

Design Patterns

Singleton Pattern

The Workspace uses the singleton pattern for global access:
class Workspace {
public:
    static Workspace& get() {
        static Workspace instance;
        return instance;
    }
    
    Workspace(const Workspace&) = delete;
    Workspace& operator=(const Workspace&) = delete;
    
private:
    Workspace() : resources({}), resourceGroups({}), rootPath(std::nullopt) {}
    ~Workspace() = default;
};
Source: workspace.h:91-170

Resource Deduplication

Resources are deduplicated by name:
Resource Workspace::createResource(const fs::path& path, 
                                   const std::string& name,
                                   ResourceType type) {
    // Check for existing resource
    for (const auto& res : resources) {
        if (res.name == name) {
            atlas_log("Resource already exists: " + name);
            return res; // Return existing
        }
    }
    // Create new resource...
}
Source: workspace.cpp:13-33

Integration with Other Systems

With Tracer

Workspace operations are logged via Tracer:
#include "atlas/tracer/log.h"

// All workspace operations use tracer macros
atlas_log("Creating resource: " + name);      // workspace.cpp:21
atlas_warning("Resource not found: " + name); // workspace.cpp:52
atlas_error("Resource not found in group: " + name); // workspace.cpp:88

With Texture System

Texture loading integrates with workspace:
// Get texture from workspace
Resource texResource = workspace.getResource("BrickTexture");

// Load through texture system
Texture tex = Texture::fromFile(texResource.path.string());

With Model System

Model loading uses workspace paths:
Resource modelResource = workspace.getResource("Character");
Model model = Model::load(modelResource.path.string());

Best Practices

Set Root Path Early

Configure the root path at application startup for consistent path resolution

Use Descriptive Names

Give resources clear, unique names for easy retrieval

Group Related Assets

Use ResourceGroups to organize assets that belong together

Check for Existence

Always verify resources exist before using them
Resource names are used for deduplication, so creating a resource with an existing name returns the original resource rather than creating a duplicate.
The Workspace system uses std::filesystem for path operations, providing cross-platform path handling. All paths are stored as fs::path objects (workspace.h:34).

Build docs developers (and LLMs) love