Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • gamedev/fggl
  • onuralpsezer/fggl
2 results
Show changes
Showing
with 330 additions and 66 deletions
......@@ -15,7 +15,7 @@
#ifndef FGGL_INCLUDE_FGGL_APP_HPP
#define FGGL_INCLUDE_FGGL_APP_HPP
#define assertm(exp, msg) assert(((void)(msg), exp))
#define ASSERT_MSG(exp, msg) assert(((void)(msg), exp))
#include <cassert>
#include <string>
......@@ -44,6 +44,8 @@ namespace fggl {
* A state is responsible for managing user interaction with the app. When created, the appstate
* is passed a reference to the application that owns it. The lifetime of the state is bounded
* by the lifetype of this object.
*
* Ie. the lifetime of reference to the App is at least that of the AppState.
*
* @param owner a non-owned reference to the owner of the state.
*/
......@@ -72,8 +74,24 @@ namespace fggl {
*/
virtual void render(gfx::Graphics &paint) = 0;
/**
* Notify the State it has just been switched to.
*
* This state should perform any setup it requires (eg, registering callbacks, requesting
* resources, etc...).
*
* FIXME: This should probably be RAII
*/
virtual void activate() {}
/**
* Notify the State it is about to be switched from.
*
* This state should perform any cleanup it requires (eg, unregistering callbacks, freeing
* resources, etc...).
*
* FIXME: This should probably be RAII
*/
virtual void deactivate() {}
[[nodiscard]] inline auto owner() const -> App& {
......@@ -84,10 +102,28 @@ namespace fggl {
App &m_owner;
};
/*! \class App app.hpp fggl/app.hpp
*
* Main entrypoint to the game framework.
*/
class App {
public:
explicit App(modules::Manager *serivces, const Identifer &name);
App(modules::Manager *services, const Identifer &name, const Identifer &folderName);
/**
* Create an instance of an application, with a set of modules and name.
*
* The name is used to derrive verious settings, such as the location of same games and user
* configuration and content.
*/
explicit App(modules::Manager *manager, const Identifer &name);
/**
* Create an instance of an application, with a set of modules and name.
*
* This version of the constructor allows explicitly setting the name used for user-data rather
* than derriving it from the application name. This is useful if you want to use a shortened
* version of your application name for configuration.
*/
App(modules::Manager *manager, const Identifer &name, const Identifer &folderName);
// class is non copy-able
App(const App &app) = delete;
......@@ -96,32 +132,65 @@ namespace fggl {
auto operator=(const App &other) -> App & = delete;
auto operator=(App &&other) -> App & = delete;
/**
* Set the currently active window.
*
* FIXME: this is a nasty hack to get round the setup order for windows, graphics APIs and
* screen refreshes. Should be fixed with observer pattern.
*/
inline void setWindow(display::Window *window) {
m_window = window;
}
/**
* Perform main game loop functions.
*
* You should pass in argc and argv used to invoke main to this method. At the moment it does
* not use these, but in the future they will be used to provide game-agnostic options.
*/
auto run(int argc, const char **argv) -> int;
/**
* Register a new state with the application.
*
* States are intended to be a block of code which can be started, will execute a single frame
* of work, and can be invoked repeatedly. For example, a main menu, options menu,
* single-player game and multi-player game might be all be suitable to be states. When a state
* change is requested, the currently running state is stopped and garabage collected.
*
* This is similar to the concept of an 'activity' in Android.
*/
template<typename T>
auto addState(const Identifer &name) -> T * {
static_assert(std::is_base_of<AppState, T>::value, "States must be AppStates");
return m_states.put<T>(name, *this);
}
/**
* Request the game changes states.
*
* This will be executed at the next opporunity (most likley next iteration of the game loop).
* Identifer should be an identifier used when calling addState, and the state should previouslly
* been registered using addState.
*/
inline void change_state(const Identifer &name) {
m_expectedScene = name;
/*m_states.active().deactivate();
m_states.change(name);
m_states.active().activate();*/
}
/**
* Return the currently active state (the state that is currently executing).
*/
inline auto active_state() const -> AppState & {
return m_states.active();
}
/**
* Get a pointer to a service.
*
* This is the primary way in which states can get access to resources they require.
*
* returns nullptr if the service does not exist, or cannot be provided.
*/
template<typename T>
inline auto service() -> T * {
try {
......
......@@ -46,7 +46,7 @@ namespace fggl::assets {
class Loader {
public:
constexpr const static modules::ModuleService service = modules::make_service("fggl::assets::Loader");
constexpr const static auto service = modules::make_service("fggl::assets::Loader");
explicit inline Loader(data::Storage *storage, CheckinAdapted *checkin) : m_storage(storage), m_checkin(checkin) {}
......@@ -80,39 +80,23 @@ namespace fggl::assets {
return;
}
// FIXME use the fancy dependency resolution algorithm from the module system
std::vector<AssetID> loadOrder;
std::stack<AssetID> openSet;
openSet.push(asset);
while ( !openSet.empty() ) {
auto current = openSet.top();
openSet.pop();
if ( !m_checkin->exists(current) ) {
debug::warning("attempted to load chain with unknown assert, {} - abort!", current);
return;
}
// this WILL (probably) break if an asset ends up in the chain twice
loadOrder.push_back( current );
const auto& record = m_checkin->find(current);
for ( const auto& dep : record.m_requires ) {
openSet.push( dep );
}
}
std::queue<AssetID> loadOrder;
m_checkin->loadOrder(asset, loadOrder);
processChain(loadOrder, userPtr);
}
void processChain( std::vector<AssetID>& loadOrder, void* userPtr = nullptr ) {
void processChain( std::queue<AssetID>& loadOrder, void* userPtr = nullptr ) {
if ( loadOrder.empty() ) {
return;
}
debug::info("Starting chain load");
for ( auto it = loadOrder.rbegin(); it < loadOrder.rend(); ++it ) {
debug::info(" CHAIN -> loading {}", *it );
load( *it, userPtr );
while( !loadOrder.empty() ) {
auto it = loadOrder.front();
debug::info(" CHAIN -> loading {}", it );
load( it, userPtr );
loadOrder.pop();
}
debug::info("Ended chain loader");
}
......
......@@ -41,6 +41,24 @@ namespace fggl::assets {
struct AssetBoxT : public AssetBox {
T* asset = nullptr;
explicit inline AssetBoxT(T* aasset) : asset(aasset) {}
// no copy: we own our resource!
AssetBoxT(const AssetBoxT&) = delete;
AssetBoxT& operator=(const AssetBoxT&) = delete;
// move OK - we can steal the asset
AssetBoxT(AssetBoxT&& other) : asset(other.asset) {
other.asset = nullptr;
}
inline ~AssetBoxT() override {
if ( asset != nullptr ) {
delete asset;
asset = nullptr;
}
}
inline void release() override {
asset = nullptr;
}
......@@ -48,7 +66,7 @@ namespace fggl::assets {
class AssetManager {
public:
constexpr const static modules::ModuleService service = modules::make_service("fggl::assets::Manager");
constexpr const static modules::ServiceName service = modules::make_service("fggl::assets::Manager");
AssetManager() = default;
virtual ~AssetManager() = default;
......@@ -74,10 +92,21 @@ namespace fggl::assets {
}
}
/**
* Pass ownership of the asset to the asset system.
*
* Once this method is called, the asset system owns the asset, and will free
* it when it is no longer required. The asset system assumes it is fully aware
* of all usages of the assets it manages via its graph.
*
* @tparam T the asset type to be managed
* @param guid the asset name
* @param assetRef the asset itself
* @return the owned asset pointer
*/
template<typename T>
T* set(const AssetID &guid, T* assetRef) {
auto ptr = std::make_shared<AssetBoxT<T>>();
ptr->asset = assetRef;
auto ptr = std::make_shared<AssetBoxT<T>>(assetRef);
m_registry[guid] = ptr;
return (*ptr).asset;
......
......@@ -31,15 +31,15 @@ namespace fggl::assets {
struct AssetFolders {
constexpr static const char *name = "fggl::assets::Folders";
constexpr static const std::array<modules::ModuleService, 2> provides = {
constexpr static const std::array<modules::ServiceName, 2> provides = {
Loader::service,
AssetManager::service
};
constexpr static const std::array<modules::ModuleService, 2> depends = {
constexpr static const std::array<modules::ServiceName, 2> depends = {
data::Storage::service,
CheckinAdapted::service
};
static bool factory(modules::ModuleService name, modules::Services &serviceManager);
static bool factory(modules::ServiceName name, modules::Services &serviceManager);
};
} // namespace fggl::assets
......
......@@ -25,6 +25,7 @@
#include "fggl/assets/packed/direct.hpp"
#include "fggl/data/storage.hpp"
#include "fggl/ds/graph.hpp"
namespace fggl::assets {
......@@ -64,7 +65,7 @@ namespace fggl::assets {
*/
class CheckinAdapted {
public:
constexpr const static modules::ModuleService service = modules::make_service("fggl::assets::checkin::debug");
constexpr const static auto service = modules::make_service("fggl::assets::checkin::debug");
using FilePredicate = std::function<AssetTypeID(const std::filesystem::path&)>;
using FileLoader = std::function<bool(const std::filesystem::path&, MemoryBlock& block)>;
......@@ -95,6 +96,10 @@ namespace fggl::assets {
return file.m_path;
}
void loadOrder( AssetID id, std::queue<AssetID>& order) {
m_requires.getOrderPartialRev(id, order);
}
void discover( const char* packName, bool useCache = false, bool updateCache = true) {
if ( m_packs.contains(packName) ) {
return;
......@@ -167,6 +172,7 @@ namespace fggl::assets {
data::Storage* m_storage;
RawCheckin* m_checkin;
std::map<AssetID, ResourceRecord> m_files;
ds::DirectedGraph<AssetID> m_requires;
struct PackInfo {
std::filesystem::path rootDir;
......@@ -204,6 +210,8 @@ namespace fggl::assets {
// store the resulting data
m_files[rr.assetID] = rr;
packFiles.push_back( rr.assetID );
m_requires.addEdges( rr.assetID, rr.m_requires );
debug::trace("discovered {} ({}) from pack '{}'", rr.assetID, relPath.c_str(), packName.c_str() );
break;
}
......@@ -256,6 +264,7 @@ namespace fggl::assets {
};
m_files[ rr.assetID ] = rr;
m_packs[ packRoot.filename() ].assets.push_back( rr.assetID );
m_requires.addEdges( rr.assetID, depList );
debug::trace("discovered {} ({}) from pack {}", rr.assetID.get(), fullPath.c_str(), packRoot.filename().c_str() );
}
......
......@@ -37,7 +37,7 @@ namespace fggl::assets {
class RawCheckin {
public:
constexpr const static modules::ModuleService service = modules::make_service("fggl::assets::checkin");
constexpr const static auto service = modules::make_service("fggl::assets::checkin");
using DecodeAndCheckFunc = std::function<void(AssetGUID, MemoryBlock& block)>;
void check(AssetID, AssetTypeID, MemoryBlock& block) const;
......
......@@ -30,14 +30,14 @@ namespace fggl::assets {
struct PackedAssets {
constexpr static const char *name = "fggl::assets::packed";
constexpr static const std::array<modules::ModuleService, 2> provides = {
constexpr static const std::array<modules::ServiceName, 2> provides = {
RawCheckin::service,
CheckinAdapted::service
};
constexpr static const std::array<modules::ModuleService, 1> depends = {
constexpr static const std::array<modules::ServiceName, 1> depends = {
data::Storage::service
};
static bool factory(modules::ModuleService name, modules::Services &serviceManager);
static bool factory(modules::ServiceName name, modules::Services &serviceManager);
};
} // namespace fggl::assets
......
......@@ -37,6 +37,14 @@ namespace fggl::audio {
int sampleCount = -1;
T *data = nullptr;
AudioClip() = default;
AudioClip(const AudioClip&) = delete;
inline ~AudioClip() {
std::free(data);
data = nullptr;
}
[[nodiscard]]
inline int size() const {
return sampleCount * sizeof(T);
......@@ -49,7 +57,7 @@ namespace fggl::audio {
constexpr auto ASSET_CLIP_SHORT = assets::make_asset_type("Audio:Clip:Short");
constexpr auto ASSET_CLIP_BYTE = assets::make_asset_type("Audio:Clip:Byte");
constexpr modules::ModuleService SERVICE_AUDIO_PLAYBACK = modules::make_service("fggl::audio::AudioService");
constexpr auto SERVICE_AUDIO_PLAYBACK = modules::make_service("fggl::audio::AudioService");
/**
*
......@@ -57,7 +65,7 @@ namespace fggl::audio {
*/
class AudioService {
public:
constexpr static const modules::ModuleService service = SERVICE_AUDIO_PLAYBACK;
constexpr static const modules::ServiceName service = SERVICE_AUDIO_PLAYBACK;
virtual void play(const assets::AssetGUID &asset, bool looping = false) = 0;
virtual void play(const AudioClipShort &clip, bool looping = false) = 0;
......
......@@ -37,13 +37,14 @@ namespace fggl::audio {
void play(const AudioClipShort & /*clip*/, bool /*looping = false*/) override;
};
//! A dummy audio module that does nothing
struct NullAudio {
constexpr static const char *name = "fggl::audio::NULL";
constexpr static const std::array<modules::ModuleService, 1> provides = {
constexpr static const std::array<modules::ServiceName, 1> provides = {
SERVICE_AUDIO_PLAYBACK
};
constexpr static const std::array<modules::ModuleService, 0> depends = {};
bool factory(modules::ModuleService, modules::Services&);
constexpr static const std::array<modules::ServiceName, 0> depends = {};
bool factory(modules::ServiceName, modules::Services&);
};
......
......@@ -32,6 +32,7 @@
#include <iostream>
#include <memory>
//! Audio backed by openal-soft
namespace fggl::audio::openal {
constexpr uint32_t NULL_BUFFER_ID = 0;
......
......@@ -33,16 +33,17 @@ namespace fggl::audio {
constexpr auto OGG_VORBIS = assets::AssetType::make("audio/vorbis");
constexpr auto RES_OGG_VORBIS = assets::from_mime("audio/vorbis");
//! an audio module which uses openal(-soft) as a backend.
struct OpenAL {
constexpr static const char *name = "fggl::audio::OpenAL";
constexpr static const std::array<modules::ModuleService, 1> provides = {
constexpr static const std::array<modules::ServiceName, 1> provides = {
SERVICE_AUDIO_PLAYBACK
};
constexpr static const std::array<modules::ModuleService, 2> depends = {
constexpr static const std::array<modules::ServiceName, 2> depends = {
assets::AssetManager::service,
assets::CheckinAdapted::service
};
static bool factory(modules::ModuleService name, modules::Services &serviceManager);
static bool factory(modules::ServiceName name, modules::Services &serviceManager);
};
......
......@@ -51,14 +51,14 @@ namespace fggl::data::models {
struct AssimpModule {
constexpr static const char *name = "fggl::data::Assimp";
constexpr static const std::array<modules::ModuleService, 1> provides = {
constexpr static const std::array<modules::ServiceName, 1> provides = {
MODEL_PROVIDER
};
constexpr static const std::array<modules::ModuleService, 2> depends = {
constexpr static const std::array<modules::ServiceName, 2> depends = {
assets::Loader::service,
assets::CheckinAdapted::service
};
static bool factory(modules::ModuleService service, modules::Services &serviceManager);
static bool factory(modules::ServiceName service, modules::Services &serviceManager);
};
}
......
......@@ -27,11 +27,11 @@ namespace fggl::data {
struct LocalStorage {
constexpr static const char *name = "fggl::data::Storage";
constexpr static const std::array<modules::ModuleService, 1> provides = {
constexpr static const std::array<modules::ServiceName, 1> provides = {
SERVICE_STORAGE
};
constexpr static const std::array<modules::ModuleService, 0> depends = {};
static bool factory(modules::ModuleService service, modules::Services &serviceManager);
constexpr static const std::array<modules::ServiceName, 0> depends = {};
static bool factory(modules::ServiceName service, modules::Services &serviceManager);
};
} // namespace fggl::data
......
......@@ -35,11 +35,11 @@ namespace fggl::data {
enum StorageType { Data, Config, Cache };
constexpr const modules::ModuleService SERVICE_STORAGE = modules::make_service("fggl::data::Storage");
constexpr const auto SERVICE_STORAGE = modules::make_service("fggl::data::Storage");
class Storage {
public:
constexpr static modules::ModuleService service = SERVICE_STORAGE;
constexpr static auto service = SERVICE_STORAGE;
Storage(fggl::platform::EnginePaths paths) : m_paths(std::move(paths)) {}
......
......@@ -28,7 +28,14 @@ namespace fggl::data {
struct Texture2D {
math::vec2i size;
int channels;
void* data;
unsigned char* data;
Texture2D() = default;
Texture2D(const Texture2D& other) = delete;
inline ~Texture2D() {
delete data;
}
};
}
......
......@@ -63,7 +63,7 @@ namespace fggl::debug {
template<typename S, typename... Args>
void logf(const char *file, int line, const S &format, Args &&... args) {
vlog(file, line, format, fmt::make_args_checked<Args...>(format, args...));
vlog(file, line, format, fmt::make_format_args(format, args...));
}
#define info_va(format, ...) \
......
......@@ -29,18 +29,18 @@ namespace fggl::display {
struct GLFW {
constexpr static const char *name = "fggl::display::glfw";
constexpr static const std::array<modules::ModuleService, 1> provides = {
constexpr static const std::array<modules::ServiceName, 1> provides = {
WindowService::service
};
constexpr static const std::array<modules::ModuleService, 2> depends = {
constexpr static const std::array<modules::ServiceName, 2> depends = {
fggl::input::Input::service,
fggl::gfx::WindowGraphics::service
};
static bool factory(modules::ModuleService name, modules::Services &serviceManager);
static bool factory(modules::ServiceName name, modules::Services &serviceManager);
};
bool GLFW::factory(modules::ModuleService service, modules::Services &services) {
bool GLFW::factory(modules::ServiceName service, modules::Services &services) {
if (service == WindowService::service) {
auto input = services.get<input::Input>();
auto graphics = services.get<gfx::WindowGraphics>();
......
......@@ -24,6 +24,7 @@
#include "fggl/gfx/interfaces.hpp"
//! Classes responsible for interacting with display managers
namespace fggl::display {
class Window {
......@@ -59,7 +60,7 @@ namespace fggl::display {
class WindowService {
public:
constexpr static const modules::ModuleService
constexpr static const auto
service = modules::make_service("fggl::display::WindowService");
virtual Window *create() = 0;
......
/*
* This file is part of FGGL.
*
* FGGL is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* FGGL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with FGGL.
* If not, see <https://www.gnu.org/licenses/>.
*/
//
// Created by webpigeon on 25/03/23.
//
#ifndef FGGL_DS_GRAPH_HPP
#define FGGL_DS_GRAPH_HPP
#include <map>
#include <vector>
#include <queue>
#include <stack>
#include <set>
namespace fggl::ds {
template<typename T>
class DirectedGraph {
public:
/**
* Add a single edge to the graph.
*
* If the entry does not already exist, this will create the entry before adding the dependencies to it.
* If the entry already exists, this will append the provided dependencies to its existing list.
*
* @param start the entry which depends something else
* @param end the thing it depends on
*/
inline void addEdge(const T start, const T end) {
m_edges[start].push_back(end);
m_edges[end];
}
/**
* Add a single vertex to the graph.
*/
inline void addVertex(const T vertex) {
m_edges[vertex];
}
/**
* Add a series of dependencies for an entry.
*
* If the entry does not already exist, this will create the entry before adding the dependencies to it.
* If the entry already exists, this will append the provided dependencies to its existing list.
*
* @param name the entry having dependencies added
* @param dependencies the things it depends on
*/
void addEdges(const T name, const std::vector<T> &dependencies) {
auto existing = m_edges.find(name);
if (existing == m_edges.end()) {
m_edges[name] = dependencies;
} else {
existing->second.insert(existing->second.end(), dependencies.begin(), dependencies.end());
}
}
/**
* Clear all currently stored dependencies.
*
* This method will result in the dependency graph being empty, with no known modules.
*/
inline void clear() {
m_edges.clear();
}
inline auto begin() const {
return m_edges.begin();
}
inline auto end() const {
return m_edges.end();
}
bool getOrder(std::stack<T> &stack) {
std::set<T> visited{};
for (const auto &module : m_edges) {
if (!visited.contains(module.first)) {
sortUtil(module.first, visited, stack);
}
}
return true;
}
bool getOrderPartial(T first, std::stack<T> &stack) {
std::set<T> visited{};
sortUtil(first, visited, stack);
return true;
}
bool getOrderRev(std::queue<T> &stack) {
std::set<T> visited{};
for (const auto &module : m_edges) {
if (!visited.contains(module.first)) {
sortUtilRev(module.first, visited, stack);
}
}
return true;
}
bool getOrderPartialRev(T first, std::queue<T>& queue) {
std::set<T> visited{};
sortUtilRev(first, visited, queue);
return true;
}
private:
std::map<T, std::vector<T>> m_edges;
void sortUtil(T idx, std::set<T> &visited, std::stack<T> &stack) {
visited.emplace(idx);
for (auto dep : m_edges.at(idx)) {
if (!visited.contains(dep))
sortUtil(dep, visited, stack);
}
stack.push(idx);
}
void sortUtilRev(T idx, std::set<T> &visited, std::queue<T> &stack) {
visited.emplace(idx);
for (auto dep : m_edges.at(idx)) {
if (!visited.contains(dep))
sortUtilRev(dep, visited, stack);
}
stack.push(idx);
}
};
} // namespace fggl::ds
#endif //FGGL_DS_GRAPH_HPP
......@@ -44,7 +44,7 @@ namespace fggl::entity {
class EntityFactory {
public:
constexpr static const modules::ModuleService service = modules::make_service("fggl::entity:Factory");
constexpr static const modules::ServiceName service = modules::make_service("fggl::entity:Factory");
inline EntityFactory(modules::Services &services) : m_services(services) {}
......