Skip to content

Project Module

The Project module manages project configuration, resource paths, and project-wide settings in Astra. It provides a centralized way to organize and access project-level data and configurations.

Purpose

The Project module provides:

  • Project structure definition and management
  • Resource path resolution with base directory support
  • System configuration storage (Physics, Render)
  • Project lifecycle operations (create, save)
  • Project metadata management

Key Components

Project Class

The core Project class manages project state and configuration.

Key Methods:

  • create(ProjectConfig) - Creates a new project (static factory method)
  • save(ElasticArena&) - Saves the project to file
  • get_config() - Returns project configuration
  • get_project_id() - Returns unique project identifier

WARNING

The load() method is currently non-functional (implementation commented out).

ProjectConfig

Configuration structure containing project settings:

cpp
struct ProjectConfig {
    std::string name = "Untitled";
    std::string directory;
    std::string manifest;
    std::vector<WindowConfig> windows;
    std::vector<SystemConfig> systems;
    ProjectResourceConfig resources;
    ProjectSerializationConfig serialization;
};

ProjectSerializer

Handles project file serialization and deserialization:

cpp
class ProjectSerializer : public Serializer {
public:
    ProjectSerializer(Ref<Project> project, Ref<SerializationContext> ctx);
    ProjectSerializer();

    void serialize() override;
    void deserialize() override;
};
  • Uses the Streams module's SerializationContext for format-agnostic serialization
  • serialize() writes project name, directory, and resources directory
  • deserialize() reads full project manifest including windows, systems, and resources
  • Supports path prefixes: @engine/ and @project/ for base directory aliases

Architecture

Project Structure

A typical Astra project has this structure:

MyGame/
├── project.json           # Project manifest file
├── resources/             # Project resources
│   ├── scenes/           # Scene files
│   ├── textures/         # Texture files
│   ├── models/           # 3D models
│   ├── materials/        # Material definitions
│   └── shaders/          # Shader files
└── cache/                # Build cache

Manager Access

Project is accessible through the ProjectManager singleton:

cpp
// Get active project
Ref<Project> project = active_project();

// Or via manager
Ref<ProjectManager> pm = project_manager();
Ref<Project> project = pm->get_active_project();

// Access configuration
ProjectConfig& config = project->get_config();

Configuration Hierarchy

Project configurations are organized hierarchically:

ProjectConfig
    ├── WindowConfig (vector)
    ├── SystemConfig (vector)
    │   ├── PhysicsSystemConfig
    │   └── RenderSystemConfig
    ├── ProjectResourceConfig
    └── ProjectSerializationConfig

Key Features

Project Creation

Create a new project programmatically:

cpp
ProjectConfig config;
config.name = "MyGame";
config.directory = "/path/to/MyGame";
config.manifest = "project.json";
config.resources.directory = "resources";
config.serialization.format = SerializationFormat::Json;

// Create project
Ref<Project> project = Project::create(config);

// Add to manager (makes it active)
project_manager()->add_project(project);

Project Loading

Note: The Project::load() method is currently non-functional (implementation is commented out in source). Project loading must be implemented through manual deserialization:

cpp
// ProjectSerializer can deserialize from a context
// but Project::load() wrapper is not yet functional

// Access active project once loaded
Ref<Project> project = active_project();
if (project) {
    ProjectConfig& config = project->get_config();
    std::string name = config.name;
}

Resource Path Resolution

The project module provides a Path class and PathManager for resolving paths:

cpp
// Create a path relative to project resources
Ref<Path> texturePath = Path::create("textures/wall.png", BaseDirectory::Project);

// Or use user-defined literals
auto shaderPath = "shaders/pbr.axsl"_project;
auto engineAsset = "icons/logo.png"_engine;

// Resolve to absolute filesystem path
std::filesystem::path absolutePath = path_manager()->resolve(texturePath);

Path Base Directories:

  • BaseDirectory::Engine - Resolves against ASTRALIX_ASSETS_DIR (compile-time constant)
  • BaseDirectory::Project - Resolves against project directory + resources directory

Configuration Management

Access and modify project configurations:

cpp
Ref<Project> project = active_project();
ProjectConfig& config = project->get_config();

// Window configuration (vector of windows)
if (!config.windows.empty()) {
    config.windows[0].width = 1920;
    config.windows[0].height = 1080;
    config.windows[0].title = "My Game";
}

// System configurations (iterate to find specific system)
for (auto& system : config.systems) {
    if (system.type == SystemType::Render) {
        auto& renderConfig = std::get<RenderSystemConfig>(system.content);
        renderConfig.msaa.is_enabled = true;
        renderConfig.msaa.samples = 4;
    }
    if (system.type == SystemType::Physics) {
        auto& physicsConfig = std::get<PhysicsSystemConfig>(system.content);
        physicsConfig.gravity = glm::vec3(0, -9.81f, 0);
    }
}

// Resource configuration
config.resources.directory = "resources";

Project Saving

Save project changes:

cpp
ElasticArena arena;
Ref<Project> project = active_project();
project->save(arena);

Note: The save implementation currently has the FileStreamWriter call commented out in source.

Integration with Other Modules

With Application Module

Application loads the project during initialization:

cpp
void Application::init() {
    // Create project configuration
    ProjectConfig config;
    config.name = "MyGame";
    config.directory = "/path/to/MyGame";
    config.resources.directory = "resources";

    // Create and register project
    Ref<Project> project = Project::create(config);
    project_manager()->add_project(project);

    // Use project config for initialization
    ProjectConfig& cfg = project->get_config();

    // Create windows from config
    for (const auto& windowConfig : cfg.windows) {
        window_manager()->create_window(windowConfig);
    }

    // Initialize systems from config
    for (const auto& systemConfig : cfg.systems) {
        // System initialization based on type
    }
}

With Resource Module

Resource module uses project paths for asset loading:

cpp
void ResourceManager::load_texture(const std::string& name) {
    // Create path relative to project resources
    auto texturePath = Path::create("textures/" + name, BaseDirectory::Project);

    // Resolve to absolute path
    std::filesystem::path absolutePath = path_manager()->resolve(texturePath);

    // Load texture from filesystem
    load_texture_from_file(absolutePath);
}

With Streams Module

Project uses Streams for serialization:

cpp
void ProjectSerializer::serialize() {
    // Access context via operator[]
    auto ctx = (*m_context)["project"];

    // Write project fields
    ctx["name"] = m_project->get_config().name;
    ctx["directory"] = m_project->get_config().directory;
    ctx["resources"]["directory"] = m_project->get_config().resources.directory;
}

void ProjectSerializer::deserialize() {
    // Read from context
    auto ctx = (*m_context)["project"];

    // Parse fields
    std::string name = ctx["name"].as<std::string>();
    std::string directory = ctx["directory"].as<std::string>();

    // Parse windows, systems, and resources
    // ...
}

With Editor Module

Editor uses project for asset browsing:

cpp
void ContentBrowserLayer::refresh() {
    // Get project resources directory
    Ref<Project> project = active_project();
    if (!project) return;

    std::string resourcesDir = project->get_config().resources.directory;

    // Scan for assets
    scan_directory(resourcesDir);
}

Configuration Types

WindowConfig

Window and display settings:

cpp
struct WindowConfig {
    WindowID id;
    std::string title;
    bool headless;
    int height;
    int width;
};

SystemConfig

Generic system configuration with type-specific content:

cpp
struct SystemConfig {
    std::string name;
    SystemType type;
    std::variant<std::monostate, PhysicsSystemConfig, RenderSystemConfig> content;
};

enum SystemType { Physics, Render };

RenderSystemConfig

Rendering system settings:

cpp
struct RenderSystemConfig {
    std::string backend;
    MSAAConfig msaa;
    std::string window_id;
    bool headless = false;

    RendererBackend backend_to_api();
    RenderTarget::MSAA msaa_to_render_target_msaa();
};

struct MSAAConfig {
    int samples;
    bool is_enabled;
};

PhysicsSystemConfig

Physics system settings:

cpp
struct PhysicsSystemConfig {
    std::string backend;
    glm::vec3 gravity;
    std::string pvd_host = "127.0.0.1";
    int pvd_port;
    int pvd_timeout = 5;
};

ProjectResourceConfig

Resource directory configuration:

cpp
struct ProjectResourceConfig {
    std::string directory;
};

ProjectSerializationConfig

Serialization format configuration:

cpp
struct ProjectSerializationConfig {
    SerializationFormat format;

    SerializationFormat formatFromString(const std::string& format);
};

File Format

Project manifest files are stored in JSON format (typically project.json):

json
{
  "project": {
    "name": "MyGame",
    "directory": "/path/to/MyGame",
    "resources": {
      "directory": "resources"
    },
    "windows": [
      {
        "id": "main-window",
        "title": "My Game",
        "width": 1920,
        "height": 1080,
        "headless": false
      }
    ],
    "systems": [
      {
        "name": "render",
        "type": "render",
        "content": {
          "backend": "opengl",
          "window_id": "main-window",
          "headless": false,
          "msaa": {
            "is_enabled": true,
            "samples": 4
          }
        }
      },
      {
        "name": "physics",
        "type": "physics",
        "content": {
          "backend": "physx",
          "gravity": [0, -9.81, 0],
          "pvd_host": "127.0.0.1",
          "pvd_port": 5425,
          "pvd_timeout": 5
        }
      }
    ]
  }
}

Path Prefixes:

  • @engine/ - References engine assets directory
  • @project/ - References project resources directory

File Locations

/src/modules/project/
├── path.hpp                        # Path class declaration
├── path.cpp                        # Path implementation
├── project.hpp                     # Project class and config structs
├── project.cpp                     # Project implementation
├── managers/
│   ├── path-manager.hpp           # PathManager singleton
│   ├── path-manager.cpp
│   ├── project-manager.hpp        # ProjectManager singleton
│   └── project-manager.cpp
└── serializers/
    ├── path-serializer.hpp        # Path serialization (empty)
    ├── path-serializer.cpp
    ├── project-serializer.hpp     # ProjectSerializer class
    └── project-serializer.cpp
  • Application - Loads project during initialization
  • Streams - Used for project serialization
  • Engine - Uses project for scene paths
  • Editor - Provides UI for project management

Best Practices

  1. Create Early: Create and register the project before initializing other modules
  2. Use Path Class: Use the Path class with base directories instead of raw strings
  3. Check Active Project: Always check if active_project() returns non-null before use
  4. Validate Config: Validate configuration values after deserialization
  5. Use Type-Safe Access: Use std::get<> with proper type checking for SystemConfig content

Performance Considerations

  • Project creation is typically done once at startup
  • PathManager caches resolved paths internally
  • Use Path objects and resolve once rather than repeated string concatenation
  • Path resolution involves filesystem operations; cache results when possible

Common Patterns

Project Initialization

Standard project initialization flow:

cpp
int main(int argc, char** argv) {
    // Create project configuration
    ProjectConfig config;
    config.name = "MyGame";
    config.directory = "/path/to/MyGame";
    config.manifest = "project.json";
    config.resources.directory = "resources";

    // Create and register project
    Ref<Project> project = Project::create(config);
    project_manager()->add_project(project);

    // Create and run application
    Application& app = Application::get();
    app.init();
    app.run();
    app.shutdown();

    return 0;
}

Note: Project loading from file is not yet functional. Projects must be created programmatically.

Resource Path Helpers

Create helper functions for common resource paths:

cpp
namespace astralix {

std::filesystem::path get_texture_path(const std::string& name) {
    auto path = Path::create("textures/" + name, BaseDirectory::Project);
    return path_manager()->resolve(path);
}

std::filesystem::path get_model_path(const std::string& name) {
    auto path = Path::create("models/" + name, BaseDirectory::Project);
    return path_manager()->resolve(path);
}

std::filesystem::path get_shader_path(const std::string& name) {
    auto path = Path::create("shaders/" + name, BaseDirectory::Project);
    return path_manager()->resolve(path);
}

// Or use UDL for convenience
auto texturePath = "textures/wall.png"_project;
auto resolvedPath = path_manager()->resolve(texturePath);

}

Configuration Updates

Update and persist configuration changes:

cpp
void update_graphics_settings(const GraphicsSettings& settings) {
    Ref<Project> project = active_project();
    if (!project) return;

    ProjectConfig& config = project->get_config();

    // Find render system
    for (auto& system : config.systems) {
        if (system.type == SystemType::Render) {
            auto& renderConfig = std::get<RenderSystemConfig>(system.content);
            renderConfig.msaa.is_enabled = settings.msaa_enabled;
            renderConfig.msaa.samples = settings.msaa_samples;
            break;
        }
    }

    // Save project
    ElasticArena arena;
    project->save(arena);
}

Project Templates

Create projects from templates:

cpp
namespace astralix {

Ref<Project> create_2d_project(const std::string& name, const std::string& directory) {
    ProjectConfig config;
    config.name = name;
    config.directory = directory;
    config.resources.directory = "resources";

    // Add 2D-optimized render system
    SystemConfig renderSystem;
    renderSystem.name = "render";
    renderSystem.type = SystemType::Render;
    RenderSystemConfig renderConfig;
    renderConfig.backend = "opengl";
    renderConfig.msaa.is_enabled = false;
    renderSystem.content = renderConfig;
    config.systems.push_back(renderSystem);

    // 2D physics with no gravity
    SystemConfig physicsSystem;
    physicsSystem.name = "physics";
    physicsSystem.type = SystemType::Physics;
    PhysicsSystemConfig physicsConfig;
    physicsConfig.backend = "physx";
    physicsConfig.gravity = glm::vec3(0, 0, 0);
    physicsSystem.content = physicsConfig;
    config.systems.push_back(physicsSystem);

    return Project::create(config);
}

Ref<Project> create_3d_project(const std::string& name, const std::string& directory) {
    ProjectConfig config;
    config.name = name;
    config.directory = directory;
    config.resources.directory = "resources";

    // Add 3D-optimized render system
    SystemConfig renderSystem;
    renderSystem.name = "render";
    renderSystem.type = SystemType::Render;
    RenderSystemConfig renderConfig;
    renderConfig.backend = "opengl";
    renderConfig.msaa.is_enabled = true;
    renderConfig.msaa.samples = 4;
    renderSystem.content = renderConfig;
    config.systems.push_back(renderSystem);

    // 3D physics with gravity
    SystemConfig physicsSystem;
    physicsSystem.name = "physics";
    physicsSystem.type = SystemType::Physics;
    PhysicsSystemConfig physicsConfig;
    physicsConfig.backend = "physx";
    physicsConfig.gravity = glm::vec3(0, -9.81f, 0);
    physicsSystem.content = physicsConfig;
    config.systems.push_back(physicsSystem);

    return Project::create(config);
}

}

Multiple Project Management

The ProjectManager can track multiple projects:

cpp
namespace astralix {

void load_multiple_projects() {
    // Create first project
    ProjectConfig config1;
    config1.name = "Project1";
    config1.directory = "/path/to/project1";
    Ref<Project> project1 = Project::create(config1);

    // Create second project
    ProjectConfig config2;
    config2.name = "Project2";
    config2.directory = "/path/to/project2";
    Ref<Project> project2 = Project::create(config2);

    // Add both to manager
    Ref<ProjectManager> pm = project_manager();
    pm->add_project(project1);  // Becomes active
    pm->add_project(project2);  // Becomes active (replaces project1)

    // Get all projects
    std::vector<Ref<Project>> allProjects = pm->get_projects();

    // Get active project ID
    ProjectID activeId = pm->get_active_project_id();
}

}