Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@

- [ ] `cmake --preset windows-debug`
- [ ] `cmake --build --preset build-debug`
- [ ] `ctest --preset test-debug`
- [ ] Not run (reason below)

## Risks / Follow-up
Expand Down
33 changes: 33 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: CI

on:
pull_request:
types:
- opened
- synchronize
- reopened
- ready_for_review
push:
branches:
- main

permissions:
contents: read

jobs:
build-and-test:
name: Build and Test
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Configure
run: cmake -S . -B build/ci -DCMAKE_BUILD_TYPE=Debug -DBUILD_TESTING=ON -DSAFECROWD_BUILD_APP=OFF

- name: Build
run: cmake --build build/ci --parallel

- name: Test
run: ctest --test-dir build/ci --output-on-failure
56 changes: 53 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,30 @@
# AGENTS.md

## Project Overview
- SafeCrowd is a ECS game engine based crowd simulation project.
- Use Qt for UI.
- SafeCrowd is an ECS-based crowd simulation and decision-support project with a Qt desktop application.
- Keep the architecture layered: `application -> domain -> engine`.
- Product/architecture documents currently lead the implementation, so verify tracked source files before assuming a module already exists in `src/`.

## Current Repo State
- Declared CMake targets:
- `ecs_engine`
- `safecrowd_domain`
- `safecrowd_app`
- The repository currently includes build configuration, docs, UML diagrams, GitHub workflow/templates, and vendored third-party code under `external/`.
- Source roots are still expected under:
- `src/application`
- `src/domain`
- `src/engine`
- When touching build files, confirm that referenced source files are actually tracked in Git.

## Build
- Configure: `cmake --preset windows-debug`
- Build: `cmake --build --preset build-debug`
- Test: `ctest --preset test-debug`
- App target: `safecrowd_app`
- UI dependency: Qt6 via `vcpkg.json` (`qtbase`)
- If configure/build fails, check preset/Visual Studio selection and `vcpkg`/Qt availability before assuming the code change caused it.
- PR CI currently validates the engine/domain/test path with `-DSAFECROWD_BUILD_APP=OFF` for fast feedback; keep the full Qt app build healthy locally.

## Source Layout
- All C++ source files live under `src/`.
Expand All @@ -17,28 +33,62 @@
- `#include "application/..."`
- `#include "domain/..."`
- `#include "engine/..."`
- Supporting repository areas:
- `docs/` for requirements, architecture, and project-management notes
- `uml/` for PlantUML diagrams and explanations
- `.github/` for repository workflow/policy files
- `external/` for vendored dependencies that must remain in-tree

## Architecture Rules
- `engine` must not depend on `domain` or `application`.
- `domain` must not depend on Qt UI code.
- `application` is responsible for wiring UI to domain logic.
- If a change affects multiple layers, review dependency direction first and keep responsibilities explicit.

## Dependency Policy
- Prefer dependencies declared in `vcpkg.json`.
- Use `external/` only for vendored libraries that must live in-tree.
- `external/glad/` is currently tracked as vendored third-party code.
- Do not leave unused third-party code in `external/`.

## GitHub Workflow
- Use GitHub issue forms for new work items; blank issues are disabled.
- Issue types currently supported:
- `Epic` for larger parent work
- `Task` for single implementation/analysis/docs work
- GitHub Project guidance is documented in `docs/GitHub Project.md`.
- PR titles must follow `[Area] short summary`.
- Allowed PR areas:
- `Engine`
- `Domain`
- `Application`
- `Docs`
- `Build`
- `Analysis`
- `Chore`
- PR bodies should follow `.github/PULL_REQUEST_TEMPLATE.md`.
- `main` is protected:
- merge through PR only
- squash merge is the intended merge mode
- required PR check: `Validate PR`
- build/test checks should stay aligned with `.github/workflows/ci.yml`

## Editing Guidelines
- Keep changes minimal and localized.
- Preserve existing naming/style unless there is a clear reason to refactor.
- Update docs when structure or build rules change.
- Update docs when structure, build rules, or repository workflow changes.
- When changing contribution workflow files, keep `CONTRIBUTING.md` and `.github/` files aligned.

## Docs
- Architecture notes: `docs/프로젝트 구조.md`
- Project workflow notes: `docs/GitHub Project.md`
- Requirements and overview docs are under `docs/`.

## Review Priorities
- Broken build or preset mismatch
- Unit test regression or missing CTest wiring
- Missing tracked source files referenced by `CMakeLists.txt`
- Layer dependency violations
- Qt code leaking into `domain`
- Unused or confusing dependency setup
- Drift between `CONTRIBUTING.md` and `.github/` workflow/template files
83 changes: 58 additions & 25 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,22 @@ project(SafeCrowd
LANGUAGES CXX
)

include(CTest)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTORCC ON)
option(SAFECROWD_BUILD_APP "Build the Qt desktop application" ON)

if (SAFECROWD_BUILD_APP)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTORCC ON)

find_package(Qt6 CONFIG REQUIRED COMPONENTS Core Gui Widgets)
qt_standard_project_setup()
find_package(Qt6 CONFIG REQUIRED COMPONENTS Core Gui Widgets)
qt_standard_project_setup()
endif()

set(SAFECROWD_SRC_DIR "${CMAKE_CURRENT_SOURCE_DIR}/src")

Expand Down Expand Up @@ -64,23 +70,50 @@ target_link_libraries(safecrowd_domain

configure_project_target(safecrowd_domain)

add_executable(safecrowd_app
src/application/main.cpp
src/application/MainWindow.cpp
src/application/MainWindow.h
)

target_include_directories(safecrowd_app
PRIVATE
${SAFECROWD_SRC_DIR}
)

target_link_libraries(safecrowd_app
PRIVATE
safecrowd_domain
Qt6::Core
Qt6::Gui
Qt6::Widgets
)

configure_project_target(safecrowd_app)
if (BUILD_TESTING)
add_executable(safecrowd_tests
tests/TestMain.cpp
tests/TestSupport.h
tests/FrameClockTests.cpp
tests/EngineRuntimeTests.cpp
tests/SafeCrowdDomainTests.cpp
)

target_include_directories(safecrowd_tests
PRIVATE
${SAFECROWD_SRC_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/tests
)

target_link_libraries(safecrowd_tests
PRIVATE
safecrowd_domain
)

configure_project_target(safecrowd_tests)

add_test(NAME safecrowd_tests COMMAND safecrowd_tests)
endif()

if (SAFECROWD_BUILD_APP)
add_executable(safecrowd_app
src/application/main.cpp
src/application/MainWindow.cpp
src/application/MainWindow.h
)

target_include_directories(safecrowd_app
PRIVATE
${SAFECROWD_SRC_DIR}
)

target_link_libraries(safecrowd_app
PRIVATE
safecrowd_domain
Qt6::Core
Qt6::Gui
Qt6::Widgets
)

configure_project_target(safecrowd_app)
endif()
18 changes: 18 additions & 0 deletions CMakePresets.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,23 @@
"configurePreset": "windows-release",
"configuration": "Release"
}
],
"testPresets": [
{
"name": "test-debug",
"configurePreset": "windows-debug",
"configuration": "Debug",
"output": {
"outputOnFailure": true
}
},
{
"name": "test-release",
"configurePreset": "windows-release",
"configuration": "Release",
"output": {
"outputOnFailure": true
}
}
]
}
3 changes: 2 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ PR 제목은 아래 형식을 따릅니다.
- 연결된 issue
- 변경이 속한 영역
- 아키텍처 규칙 점검 결과
- 빌드/검증 결과 또는 미실행 사유
- 빌드/테스트 검증 결과 또는 미실행 사유
- 남은 리스크나 후속 작업

## 아키텍처 체크
Expand All @@ -71,6 +71,7 @@ PR 작성 시 아래 항목을 항상 점검합니다.
```powershell
cmake --preset windows-debug
cmake --build --preset build-debug
ctest --preset test-debug
```

실행하지 못했다면 PR 본문에 이유를 남깁니다.
Expand Down
99 changes: 99 additions & 0 deletions src/application/MainWindow.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
#include "application/MainWindow.h"

#include <QLabel>
#include <QPushButton>
#include <QTimer>
#include <QVBoxLayout>
#include <QWidget>

#include "domain/SafeCrowdDomain.h"
#include "engine/EngineState.h"

namespace {

QString stateToString(safecrowd::engine::EngineState state) {
using safecrowd::engine::EngineState;

switch (state) {
case EngineState::Stopped:
return "Stopped";
case EngineState::Ready:
return "Ready";
case EngineState::Running:
return "Running";
case EngineState::Paused:
return "Paused";
}

return "Unknown";
}

} // namespace

namespace safecrowd::application {

MainWindow::MainWindow(safecrowd::domain::SafeCrowdDomain& domain, QWidget* parent)
: QMainWindow(parent),
domain_(domain) {
auto* centralWidget = new QWidget(this);
auto* layout = new QVBoxLayout(centralWidget);
statusLabel_ = new QLabel(this);

auto* startButton = new QPushButton("Start", this);
auto* pauseButton = new QPushButton("Pause", this);
auto* stopButton = new QPushButton("Stop", this);

layout->addWidget(statusLabel_);
layout->addWidget(startButton);
layout->addWidget(pauseButton);
layout->addWidget(stopButton);

tickTimer_ = new QTimer(this);
tickTimer_->setInterval(16);

connect(startButton, &QPushButton::clicked, this, [this]() { startSimulation(); });
connect(pauseButton, &QPushButton::clicked, this, [this]() { pauseSimulation(); });
connect(stopButton, &QPushButton::clicked, this, [this]() { stopSimulation(); });
connect(tickTimer_, &QTimer::timeout, this, [this]() { tickSimulation(); });

setCentralWidget(centralWidget);
setWindowTitle("SafeCrowd");
resize(420, 220);

refreshStatusLabel();
}

void MainWindow::startSimulation() {
domain_.start();
tickTimer_->start();
refreshStatusLabel();
}

void MainWindow::pauseSimulation() {
domain_.pause();
tickTimer_->stop();
refreshStatusLabel();
}

void MainWindow::stopSimulation() {
domain_.stop();
tickTimer_->stop();
refreshStatusLabel();
}

void MainWindow::tickSimulation() {
domain_.update(1.0 / 60.0);
refreshStatusLabel();
}

void MainWindow::refreshStatusLabel() {
const auto summary = domain_.summary();
statusLabel_->setText(
QString("State: %1\nFrames: %2\nFixed Steps: %3\nAlpha: %4")
.arg(stateToString(summary.state))
.arg(summary.frameIndex)
.arg(summary.fixedStepIndex)
.arg(summary.alpha, 0, 'f', 2));
}

} // namespace safecrowd::application
Loading
Loading