[keel,nostalgia,studio,turbine] Cleanup structure of Turbine

This commit is contained in:
Gary Talent 2023-12-08 23:59:02 -06:00
parent 1298051a1a
commit facde6bdce
60 changed files with 669 additions and 557 deletions

View File

@ -134,7 +134,7 @@ ox::Error preloadDir(
}
template<typename PlatSpec>
ox::Error appendBinary(ox::Buffer &binBuff, ox::Buffer &fsBuff, ox::Preloader<PlatSpec> &pl) noexcept {
ox::Error appendBinary(ox::Buffer &binBuff, ox::SpanView<char> const&fsBuff, ox::Preloader<PlatSpec> &pl) noexcept {
constexpr auto padbin = [](ox::BufferWriter &w, unsigned factor) noexcept -> ox::Error {
return w.write(nullptr, factor - w.buff().size() % factor);
};

View File

@ -9,15 +9,14 @@
namespace keel {
#ifndef OX_BARE_METAL
[[nodiscard]]
static ox::Result<const BaseConverter*> findConverter(
keel::Context &ctx,
ox::Vector<const BaseConverter*> const&converters,
ox::CRStringView srcTypeName,
int srcTypeVersion,
ox::CRStringView dstTypeName,
int dstTypeVersion) noexcept {
for (auto &c : ctx.converters) {
for (auto &c : converters) {
if (c->matches(srcTypeName, srcTypeVersion, dstTypeName, dstTypeVersion)) {
return c;
}
@ -27,23 +26,24 @@ static ox::Result<const BaseConverter*> findConverter(
static ox::Result<ox::UniquePtr<Wrap>> convert(
[[maybe_unused]] keel::Context &ctx,
ox::Vector<const BaseConverter*> const&converters,
[[maybe_unused]] const ox::Buffer &srcBuffer,
[[maybe_unused]] ox::CRStringView srcTypeName,
[[maybe_unused]] int srcTypeVersion,
[[maybe_unused]] ox::CRStringView dstTypeName,
[[maybe_unused]] int dstTypeVersion) noexcept {
// look for direct converter
auto [c, err] = findConverter(ctx, srcTypeName, srcTypeVersion, dstTypeName, dstTypeVersion);
auto [c, err] = findConverter(converters, srcTypeName, srcTypeVersion, dstTypeName, dstTypeVersion);
if (!err) {
return c->convertBuffToPtr(ctx, srcBuffer);
}
// try to chain multiple converters
for (const auto &subConverter : ctx.converters) {
for (const auto &subConverter : converters) {
if (!subConverter->dstMatches(dstTypeName, dstTypeVersion)) {
continue;
}
const auto [intermediate, chainErr] =
convert(ctx, srcBuffer, srcTypeName, srcTypeVersion,
convert(ctx, converters, srcBuffer, srcTypeName, srcTypeVersion,
subConverter->srcTypeName(), subConverter->srcTypeVersion());
if (!chainErr) {
return subConverter->convertPtrToPtr(ctx, *intermediate);
@ -51,7 +51,6 @@ static ox::Result<ox::UniquePtr<Wrap>> convert(
}
return OxError(1, "Could not convert between types");
}
#endif
ox::Result<ox::UniquePtr<Wrap>> convert(
[[maybe_unused]] keel::Context &ctx,
@ -60,7 +59,14 @@ ox::Result<ox::UniquePtr<Wrap>> convert(
[[maybe_unused]] int dstTypeVersion) noexcept {
#ifndef OX_BARE_METAL
oxRequire(hdr, readAssetHeader(srcBuffer));
return convert(ctx, srcBuffer, hdr.clawHdr.typeName, hdr.clawHdr.typeVersion, dstTypeName, dstTypeVersion);
return convert(
ctx,
ctx.converters,
srcBuffer,
hdr.clawHdr.typeName,
hdr.clawHdr.typeVersion,
dstTypeName,
dstTypeVersion);
#else
return OxError(1, "Operation not supported on this platform");
#endif

View File

@ -27,7 +27,7 @@ class Context {
};
ox::Result<ox::UPtr<Context>> init(turbine::Context &tctx, InitParams const& = {}) noexcept;
ox::Result<ox::UPtr<Context>> init(turbine::Context &tctx, InitParams const&params = {}) noexcept;
}

View File

@ -12,9 +12,10 @@ target_link_libraries(
NostalgiaCore-GBA PUBLIC
TeaGBA
Keel
Turbine
)
if(TURBINE_BUILD_TYPE STREQUAL "GBA")
set_source_files_properties(gfx.cpp PROPERTIES COMPILE_FLAGS -marm)
target_link_libraries(NostalgiaCore PUBLIC NostalgiaCore-GBA)
endif()
endif()

View File

@ -15,8 +15,8 @@ struct GbaContext: public core::Context {
explicit GbaContext(turbine::Context &tctx) noexcept;
[[nodiscard]]
auto const&rom() const noexcept {
return *turbine::rom(turbineCtx);
ox::MemFS const&rom() const noexcept {
return static_cast<ox::MemFS const&>(*turbine::rom(turbineCtx));
}
};

View File

@ -142,7 +142,7 @@ ox::Error loadBgTileSheet(
ox::FileAddress const&tilesheetAddr,
ox::FileAddress const&paletteAddr) noexcept {
auto &gctx = static_cast<GbaContext&>(ctx);
auto &rom = static_cast<const ox::MemFS&>(gctx.rom());
auto &rom = gctx.rom();
return loadBgTileSheet(rom, cbb, tilesheetAddr, paletteAddr);
}
@ -151,7 +151,7 @@ ox::Error loadSpriteTileSheet(
ox::FileAddress const&tilesheetAddr,
ox::FileAddress const&paletteAddr) noexcept {
auto &gctx = static_cast<GbaContext&>(ctx);
auto &rom = static_cast<const ox::MemFS&>(gctx.rom());
auto &rom = gctx.rom();
oxRequire(tsStat, gctx.rom().stat(tilesheetAddr));
oxRequire(ts, rom.directAccess(tilesheetAddr));
GbaTileMapTarget target;
@ -169,7 +169,7 @@ ox::Error loadSpriteTileSheet(
ox::Error loadBgPalette(Context &ctx, unsigned, ox::FileAddress const&paletteAddr) noexcept {
auto &gctx = static_cast<GbaContext&>(ctx);
auto &rom = static_cast<const ox::MemFS&>(gctx.rom());
auto &rom = gctx.rom();
GbaPaletteTarget target;
target.palette = MEM_BG_PALETTE;
oxRequire(palStat, gctx.rom().stat(paletteAddr));
@ -180,7 +180,7 @@ ox::Error loadBgPalette(Context &ctx, unsigned, ox::FileAddress const&paletteAdd
ox::Error loadSpritePalette(Context &ctx, unsigned cbb, ox::FileAddress const&paletteAddr) noexcept {
auto &gctx = static_cast<GbaContext&>(ctx);
auto &rom = static_cast<const ox::MemFS&>(gctx.rom());
auto &rom = gctx.rom();
GbaPaletteTarget target;
target.palette = &MEM_SPRITE_PALETTE[cbb];
oxRequire(palStat, rom.stat(paletteAddr));

View File

@ -43,7 +43,7 @@ ox::Array<char, 128> charMap = {
0, // space
38, // !
0, // "
0, // #
49, // #
0, // $
0, // %
0, // &
@ -132,9 +132,9 @@ ox::Array<char, 128> charMap = {
25, // y
26, // z
46, // {
0, // |
51, // |
48, // }
0, // ~
50, // ~
};
void setSprite(Context &c, Sprite const&s) noexcept {

View File

@ -435,7 +435,7 @@ ox::Error loadBgTileSheet(
ox::FileAddress const&tilesheetAddr,
ox::FileAddress const&paletteAddr) noexcept {
auto &gctx = glctx(ctx);
auto &kctx = gctx.turbineCtx.keelCtx;
auto &kctx = keelCtx(gctx.turbineCtx);
oxRequire(tilesheet, readObj<CompactTileSheet>(kctx, tilesheetAddr));
oxRequire(palette, readObj<Palette>(kctx, paletteAddr ? paletteAddr : tilesheet->defaultPalette));
oxRequire(tsd, loadTileSheet(ctx, *tilesheet).to([](TileSheetData const&t) -> TileSheetData {
@ -455,7 +455,7 @@ ox::Error loadSpriteTileSheet(
ox::FileAddress const&tilesheetAddr,
ox::FileAddress const&paletteAddr) noexcept {
auto &gctx = glctx(ctx);
auto &kctx = gctx.turbineCtx.keelCtx;
auto &kctx = keelCtx(gctx.turbineCtx);
oxRequire(tilesheet, readObj<CompactTileSheet>(kctx, tilesheetAddr));
oxRequire(palette, readObj<Palette>(kctx, paletteAddr ? paletteAddr : tilesheet->defaultPalette));
oxRequire(tsd, loadTileSheet(ctx, *tilesheet));

View File

@ -19,7 +19,7 @@ constexpr uint64_t TileColumns = 128;
constexpr uint64_t TileCount = TileRows * TileColumns;
constexpr uint64_t SpriteCount = 128;
constexpr uint64_t BgVertexVboRows = 4;
constexpr uint64_t BgVertexVboRowLength = 4;
constexpr uint64_t BgVertexVboRowLength = 5;
constexpr uint64_t BgVertexVboLength = BgVertexVboRows * BgVertexVboRowLength;
constexpr uint64_t BgVertexEboLength = 6;
constexpr uint64_t SpriteVertexVboRows = 256;

View File

@ -17,9 +17,9 @@ namespace nostalgia::core {
PaletteEditorImGui::PaletteEditorImGui(turbine::Context &ctx, ox::CRStringView path):
m_ctx(ctx),
m_itemPath(std::move(path)),
m_itemPath(path),
m_itemName(m_itemPath.substr(std::find(m_itemPath.rbegin(), m_itemPath.rend(), '/').offset() + 1)),
m_pal(*keel::readObj<Palette>(m_ctx.keelCtx, ox::FileAddress(m_itemPath.c_str())).unwrapThrow()) {
m_pal(*keel::readObj<Palette>(keelCtx(m_ctx), ox::FileAddress(m_itemPath.c_str())).unwrapThrow()) {
}
ox::String const&PaletteEditorImGui::itemName() const noexcept {

View File

@ -567,10 +567,10 @@ class PaletteChangeCommand: public TileSheetCommand {
TileSheetEditorModel::TileSheetEditorModel(turbine::Context &ctx, ox::StringView path):
m_ctx(ctx),
m_path(path) {
oxRequireT(img, readObj<TileSheet>(m_ctx.keelCtx, m_path));
oxRequireT(img, readObj<TileSheet>(keelCtx(m_ctx), m_path));
m_img = *img;
if (m_img.defaultPalette) {
oxThrowError(readObj<Palette>(m_ctx.keelCtx, m_img.defaultPalette).moveTo(&m_pal));
oxThrowError(readObj<Palette>(keelCtx(m_ctx), m_img.defaultPalette).moveTo(&m_pal));
}
m_pal.updated.connect(this, &TileSheetEditorModel::markUpdated);
m_undoStack.changeTriggered.connect(this, &TileSheetEditorModel::markUpdatedCmdId);
@ -634,7 +634,7 @@ ox::StringView TileSheetEditorModel::palPath() const noexcept {
constexpr ox::StringView uuidPrefix = "uuid://";
if (ox::beginsWith(path, uuidPrefix)) {
auto uuid = ox::StringView(path.data() + uuidPrefix.bytes(), path.bytes() - uuidPrefix.bytes());
auto out = m_ctx.keelCtx.uuidToPath.at(uuid);
auto out = keelCtx(m_ctx).uuidToPath.at(uuid);
if (out.error) {
return {};
}
@ -645,7 +645,7 @@ ox::StringView TileSheetEditorModel::palPath() const noexcept {
}
ox::Error TileSheetEditorModel::setPalette(ox::String const&path) noexcept {
oxRequire(uuid, m_ctx.keelCtx.pathToUuid.at(path));
oxRequire(uuid, keelCtx(m_ctx).pathToUuid.at(path));
pushCommand(ox::make<PaletteChangeCommand>(activeSubSheetIdx(), m_img, uuid->toString()));
return {};
}
@ -753,7 +753,7 @@ ox::Error TileSheetEditorModel::markUpdatedCmdId(const studio::UndoCommand *cmd)
m_updated = true;
const auto cmdId = cmd->commandId();
if (static_cast<CommandId>(cmdId) == CommandId::PaletteChange) {
oxReturnError(readObj<Palette>(m_ctx.keelCtx, ox::StringView(m_img.defaultPalette.getPath().value)).moveTo(&m_pal));
oxReturnError(readObj<Palette>(keelCtx(m_ctx), ox::StringView(m_img.defaultPalette.getPath().value)).moveTo(&m_pal));
}
auto tsCmd = dynamic_cast<const TileSheetCommand*>(cmd);
auto idx = m_img.validateSubSheetIdx(tsCmd->subsheetIdx());

View File

@ -12,7 +12,7 @@ namespace nostalgia::scene {
class Scene {
private:
const SceneStatic &m_sceneStatic;
SceneStatic const&m_sceneStatic;
public:
explicit Scene(SceneStatic const&sceneStatic) noexcept;

View File

@ -37,7 +37,7 @@ struct TileDoc {
ox::Array<uint8_t, 4> layerAttachments;
[[nodiscard]]
constexpr ox::Result<core::SubSheetId> getSubsheetId(const core::TileSheet &ts) const noexcept {
constexpr ox::Result<core::SubSheetId> getSubsheetId(core::TileSheet const&ts) const noexcept {
// prefer the already present ID
if (subsheetId > -1) {
return subsheetId;
@ -47,7 +47,7 @@ struct TileDoc {
[[nodiscard]]
constexpr ox::Result<ox::StringView> getSubsheetPath(
const core::TileSheet &ts) const noexcept {
core::TileSheet const&ts) const noexcept {
// prefer the already present path
if (!subsheetPath.len()) {
return ts.getNameFor(subsheetId);
@ -88,7 +88,7 @@ struct SceneDoc {
auto colCnt = layer[0].size();
// find shortest row (they should all be the same, but you know this data
// could come from a file)
for (const auto &row : layer) {
for (auto const&row : layer) {
colCnt = ox::min(colCnt, row.size());
}
return {static_cast<int>(colCnt), rowCnt};

View File

@ -16,12 +16,12 @@ ox::Error Scene::setupDisplay(core::Context &ctx) const noexcept {
if (m_sceneStatic.palettes.empty()) {
return OxError(1, "Scene has no palettes");
}
const auto &palette = m_sceneStatic.palettes[0];
auto const&palette = m_sceneStatic.palettes[0];
oxReturnError(core::loadBgTileSheet(
ctx, 0, m_sceneStatic.tilesheet, palette));
// disable all backgrounds
core::setBgStatus(ctx, 0);
for (auto layerNo = 0u; const auto &layer : m_sceneStatic.tileMapIdx) {
for (auto layerNo = 0u; auto const&layer : m_sceneStatic.tileMapIdx) {
setupLayer(ctx, layer, layerNo);
++layerNo;
}
@ -37,7 +37,7 @@ void Scene::setupLayer(
auto x = 0;
auto y = 0;
const auto width = m_sceneStatic.rows[layerNo];
for (const auto &tile : layer) {
for (auto const&tile : layer) {
const auto tile8 = static_cast<uint8_t>(tile);
core::setTile(ctx, layerNo, x, y, tile8);
core::setTile(ctx, layerNo, x + 1, y, tile8 + 1);

View File

@ -50,7 +50,7 @@ void SceneEditorImGui::onActivated() noexcept {
ox::Error SceneEditorImGui::saveItem() noexcept {
const auto sctx = applicationData<studio::StudioContext>(m_ctx);
oxReturnError(sctx->project->writeObj(m_itemPath, m_editor.scene()));
oxReturnError(m_ctx.keelCtx.assetManager.setAsset(m_itemPath, m_editor.scene()));
oxReturnError(keelCtx(m_ctx).assetManager.setAsset(m_itemPath, m_editor.scene()));
return {};
}

View File

@ -10,7 +10,7 @@ namespace nostalgia::scene {
SceneEditor::SceneEditor(turbine::Context &ctx, ox::CRStringView path):
m_ctx(ctx),
m_scene(*keel::readObj<SceneStatic>(m_ctx.keelCtx, path).unwrapThrow()) {
m_scene(*keel::readObj<SceneStatic>(keelCtx(m_ctx), path).unwrapThrow()) {
}
}

View File

@ -24,7 +24,7 @@ class StudioModule: public studio::Module {
}
};
static StudioModule mod;
static StudioModule const mod;
const studio::Module *studioModule() noexcept {
return &mod;
}

View File

@ -36,7 +36,7 @@ ox::Error run(ox::UniquePtr<ox::FileSystem> &&fs) noexcept {
oxRequireM(tctx, turbine::init(std::move(fs), "Nostalgia"));
oxRequireM(cctx, core::init(*tctx));
constexpr ox::FileAddress SceneAddr = ox::StringLiteral("/Scenes/Chester.nscn");
oxRequire(scn, keel::readObj<scene::SceneStatic>(tctx->keelCtx, SceneAddr));
oxRequire(scn, keel::readObj<scene::SceneStatic>(keelCtx(*tctx), SceneAddr));
turbine::setUpdateHandler(*tctx, updateHandler);
turbine::setKeyEventHandler(*tctx, keyEventHandler);
s_scene.emplace(*scn);

View File

@ -10,7 +10,7 @@
namespace studio {
AboutPopup::AboutPopup(turbine::Context &ctx) noexcept {
m_text = ox::sfmt("{} - dev build", ctx.keelCtx.appName);
m_text = ox::sfmt("{} - dev build", keelCtx(ctx).appName);
}
void AboutPopup::open() noexcept {

View File

@ -47,7 +47,7 @@ static ox::Error runApp(
ox::CRStringView projectDataDir,
ox::UniquePtr<ox::FileSystem> fs) noexcept {
oxRequireM(ctx, turbine::init(std::move(fs), appName));
turbine::setWindowTitle(*ctx, ctx->keelCtx.appName);
turbine::setWindowTitle(*ctx, keelCtx(*ctx).appName);
turbine::setUpdateHandler(*ctx, updateHandler);
turbine::setKeyEventHandler(*ctx, keyEventHandler);
turbine::setConstantRefresh(*ctx, false);

View File

@ -26,7 +26,7 @@ class ProjectExplorer: public studio::Widget {
ox::Error refreshProjectTreeModel(ox::CRStringView = {}) noexcept;
[[nodiscard]]
constexpr ox::FileSystem *romFs() noexcept {
inline ox::FileSystem *romFs() noexcept {
return rom(*m_ctx);
}

View File

@ -42,7 +42,7 @@ StudioUI::StudioUI(turbine::Context *ctx, ox::StringView projectDir) noexcept:
ImGui::GetIO().IniFilename = nullptr;
loadModules();
// open project and files
const auto [config, err] = studio::readConfig<StudioConfig>(&ctx->keelCtx);
const auto [config, err] = studio::readConfig<StudioConfig>(&keelCtx(*ctx));
m_showProjectExplorer = config.showProjectExplorer;
if (!err) {
oxIgnoreError(openProject(config.projectPath));
@ -223,7 +223,7 @@ void StudioUI::drawTabs() noexcept {
if (ImGui::BeginTabItem(e->itemDisplayName().c_str(), &open, flags)) {
if (m_activeEditor != e.get()) {
m_activeEditor = e.get();
studio::editConfig<StudioConfig>(&m_ctx->keelCtx, [&](StudioConfig *config) {
studio::editConfig<StudioConfig>(&keelCtx(*m_ctx), [&](StudioConfig *config) {
config->activeTabItemName = m_activeEditor->itemName();
});
}
@ -276,7 +276,7 @@ void StudioUI::loadModules() noexcept {
void StudioUI::toggleProjectExplorer() noexcept {
m_showProjectExplorer = !m_showProjectExplorer;
studio::editConfig<StudioConfig>(&m_ctx->keelCtx, [&](StudioConfig *config) {
studio::editConfig<StudioConfig>(&keelCtx(*m_ctx), [&](StudioConfig *config) {
config->showProjectExplorer = m_showProjectExplorer;
});
}
@ -303,16 +303,16 @@ void StudioUI::save() noexcept {
ox::Error StudioUI::openProject(ox::CRStringView path) noexcept {
oxRequireM(fs, keel::loadRomFs(path));
oxReturnError(keel::setRomFs(m_ctx->keelCtx, std::move(fs)));
turbine::setWindowTitle(*m_ctx, ox::sfmt("{} - {}", m_ctx->keelCtx.appName, path));
m_project = ox::make_unique<studio::Project>(m_ctx->keelCtx, ox::String(path), m_projectDir);
oxReturnError(keel::setRomFs(keelCtx(*m_ctx), std::move(fs)));
turbine::setWindowTitle(*m_ctx, ox::sfmt("{} - {}", keelCtx(*m_ctx).appName, path));
m_project = ox::make_unique<studio::Project>(keelCtx(*m_ctx), ox::String(path), m_projectDir);
auto sctx = applicationData<studio::StudioContext>(*m_ctx);
sctx->project = m_project.get();
m_project->fileAdded.connect(m_projectExplorer.get(), &ProjectExplorer::refreshProjectTreeModel);
m_project->fileDeleted.connect(m_projectExplorer.get(), &ProjectExplorer::refreshProjectTreeModel);
m_openFiles.clear();
m_editors.clear();
studio::editConfig<StudioConfig>(&m_ctx->keelCtx, [&](StudioConfig *config) {
studio::editConfig<StudioConfig>(&keelCtx(*m_ctx), [&](StudioConfig *config) {
config->projectPath = ox::String(path);
config->openFiles.clear();
});
@ -362,7 +362,7 @@ ox::Error StudioUI::openFileActiveTab(ox::CRStringView path, bool makeActiveTab)
m_activeEditorUpdatePending = editor;
}
// save to config
studio::editConfig<StudioConfig>(&m_ctx->keelCtx, [&](StudioConfig *config) {
studio::editConfig<StudioConfig>(&keelCtx(*m_ctx), [&](StudioConfig *config) {
if (!config->openFiles.contains(path)) {
config->openFiles.emplace_back(path);
}
@ -376,7 +376,7 @@ ox::Error StudioUI::closeFile(ox::CRStringView path) noexcept {
}
oxIgnoreError(m_openFiles.erase(std::remove(m_openFiles.begin(), m_openFiles.end(), path)));
// save to config
studio::editConfig<StudioConfig>(&m_ctx->keelCtx, [&](StudioConfig *config) {
studio::editConfig<StudioConfig>(&keelCtx(*m_ctx), [&](StudioConfig *config) {
oxIgnoreError(config->openFiles.erase(std::remove(config->openFiles.begin(), config->openFiles.end(), path)));
});
return OxError(0);

View File

@ -33,7 +33,7 @@ class ItemMakerT: public ItemMaker {
const T item;
const ox::ClawFormat fmt;
public:
constexpr explicit ItemMakerT(
constexpr ItemMakerT(
ox::StringView pDisplayName,
ox::StringView pParentDir,
ox::StringView fileExt,
@ -64,7 +64,7 @@ class ItemMakerT: public ItemMaker {
ox::Error write(turbine::Context *ctx, ox::CRStringView pName) const noexcept override {
const auto path = ox::sfmt("/{}/{}.{}", parentDir, pName, fileExt);
auto sctx = turbine::applicationData<studio::StudioContext>(*ctx);
keel::createUuidMapping(ctx->keelCtx, path, ox::UUID::generate().unwrap());
keel::createUuidMapping(keelCtx(*ctx), path, ox::UUID::generate().unwrap());
return sctx->project->writeObj(path, item, fmt);
}
};

View File

@ -1,35 +1 @@
add_library(
Turbine
event.cpp
)
add_subdirectory(gba)
if(NOT TURBINE_BUILD_TYPE STREQUAL "GBA")
add_subdirectory(glfw)
endif()
target_link_libraries(
Turbine PUBLIC
Keel
)
install(
FILES
clipboard.hpp
config.hpp
context.hpp
event.hpp
gfx.hpp
input.hpp
turbine.hpp
DESTINATION
include/turbine
)
install(
TARGETS
Turbine
DESTINATION
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
)
add_subdirectory(src)

View File

@ -1,32 +0,0 @@
/*
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#pragma once
#include <ox/claw/claw.hpp>
#include <ox/std/memory.hpp>
#include <ox/std/string.hpp>
#include "context.hpp"
namespace turbine {
ox::String getClipboardText(class Context &ctx) noexcept;
void setClipboardText(class Context &ctx, ox::CRStringView text) noexcept;
template<typename T>
void setClipboardObject([[maybe_unused]] class Context &ctx, [[maybe_unused]] ox::UniquePtr<T> obj) noexcept {
ctx.clipboard = std::move(obj);
}
template<typename T>
ox::Result<T*> getClipboardObject([[maybe_unused]] class Context &ctx) noexcept {
if (ctx.clipboard && ctx.clipboard->typeMatch(T::TypeName, T::TypeVersion)) {
return static_cast<T*>(ctx.clipboard.get());
}
return OxError(1);
}
}

View File

@ -1,25 +0,0 @@
/*
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#pragma once
#if __has_include(<imgui.h>)
#endif
#include <ox/std/types.hpp>
namespace turbine::config {
constexpr auto ImGuiEnabled =
#if __has_include(<imgui.h>)
true;
#else
false;
#endif
constexpr auto GbaEventLoopTimerBased = false;
constexpr auto GbaTimerBits = 32;
constexpr auto GlFpsPrint = false;
}

View File

@ -1,102 +0,0 @@
/*
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#pragma once
#include <ox/fs/fs.hpp>
#include <ox/model/desctypes.hpp>
#include <ox/std/buffer.hpp>
#include <ox/std/size.hpp>
#include <keel/context.hpp>
#include "event.hpp"
#include "input.hpp"
namespace turbine {
class BaseClipboardObject {
public:
virtual ~BaseClipboardObject() noexcept = default;
[[nodiscard]]
virtual ox::String typeId() const noexcept = 0;
[[nodiscard]]
constexpr auto typeMatch(auto name, auto version) const noexcept {
return typeId() == ox::buildTypeId(name, version);
}
};
template<typename T>
class ClipboardObject: public BaseClipboardObject {
[[nodiscard]]
ox::String typeId() const noexcept final {
return ox::buildTypeId(T::TypeName, T::TypeVersion);
}
};
void shutdown(Context &ctx) noexcept;
// User Input Output
class Context {
friend constexpr void setApplicationData(Context &ctx, void *applicationData) noexcept;
template<typename T>
friend constexpr T *applicationData(Context &ctx) noexcept;
friend void setUpdateHandler(Context &ctx, UpdateHandler h) noexcept;
friend constexpr void setKeyEventHandler(Context &ctx, KeyEventHandler h) noexcept;
friend constexpr KeyEventHandler keyEventHandler(Context &ctx) noexcept;
public:
UpdateHandler updateHandler = [](Context&) -> int {return 0;};
ox::UPtr<BaseClipboardObject> clipboard;
keel::Context keelCtx;
protected:
KeyEventHandler m_keyEventHandler = nullptr;
void *m_applicationData = nullptr;
Context() noexcept = default;
public:
Context(Context &other) noexcept = delete;
Context(const Context &other) noexcept = delete;
Context(const Context &&other) noexcept = delete;
virtual inline ~Context() noexcept {
shutdown(*this);
}
};
constexpr auto *rom(Context &ctx) noexcept {
return ctx.keelCtx.rom.get();
}
constexpr const auto *rom(const Context &ctx) noexcept {
return ctx.keelCtx.rom.get();
}
constexpr void setApplicationData(Context &ctx, void *applicationData) noexcept {
ctx.m_applicationData = applicationData;
}
template<typename T>
[[nodiscard]]
constexpr T *applicationData(Context &ctx) noexcept {
return static_cast<T*>(ctx.m_applicationData);
}
constexpr void setKeyEventHandler(Context &ctx, KeyEventHandler h) noexcept {
ctx.m_keyEventHandler = h;
}
constexpr KeyEventHandler keyEventHandler(Context &ctx) noexcept {
return ctx.m_keyEventHandler;
}
void setConstantRefresh(Context &ctx, bool r) noexcept;
}

View File

@ -1,29 +0,0 @@
/*
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#pragma once
#include <ox/fs/fs.hpp>
#include <ox/model/desctypes.hpp>
#include <ox/std/buffer.hpp>
#include <ox/std/size.hpp>
#include <keel/context.hpp>
#include "../context.hpp"
namespace turbine::gba {
class Context: public turbine::Context {
public:
bool running = true;
Context() noexcept = default;
Context(Context &other) noexcept = delete;
Context(const Context &other) noexcept = delete;
Context(const Context &&other) noexcept = delete;
};
}

View File

@ -1,12 +0,0 @@
target_sources(
Turbine PRIVATE
clipboard.cpp
gfx.cpp
turbine.cpp
)
target_link_libraries(
Turbine PUBLIC
glad
glfw
imgui
)

View File

@ -1,27 +0,0 @@
/*
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#include <GLFW/glfw3.h>
#include <ox/std/string.hpp>
#include <turbine/turbine.hpp>
#include "context.hpp"
namespace turbine {
ox::String getClipboardText(Context &ctx) noexcept {
auto &gctx = static_cast<GlfwContext&>(ctx);
return ox::String(glfwGetClipboardString(gctx.window));
}
void setClipboardText(Context &ctx, ox::CRStringView text) noexcept {
auto &gctx = static_cast<GlfwContext&>(ctx);
auto cstr = ox_malloca(text.bytes() + 1, char);
ox_strncpy(cstr.get(), text.data(), text.bytes());
glfwSetClipboardString(gctx.window, cstr.get());
}
}

View File

@ -1,25 +0,0 @@
/*
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#pragma once
#include <turbine/turbine.hpp>
namespace turbine {
struct GlfwContext: public turbine::Context {
int uninterruptedRefreshes = 3;
ox::UPtr<BaseClipboardObject> clipboard;
struct GLFWwindow *window = nullptr;
// sets screen refresh to constant instead of only on event
bool constantRefresh = true;
ox::Vector<gl::Drawer*, 5> drawers;
int64_t startTime = 0;
uint64_t wakeupTime = 0;
uint64_t keysDown = 0;
uint64_t prevFpsCheckTime = 0;
uint64_t draws = 0;
};
}

View File

@ -1,122 +0,0 @@
/*
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#include <GLFW/glfw3.h>
#include <imgui_impl_opengl3.h>
#include <imgui_impl_glfw.h>
#include <keel/keel.hpp>
#include <turbine/gfx.hpp>
#include "../config.hpp"
#include "context.hpp"
namespace turbine {
static void draw(GlfwContext &gctx) noexcept {
// draw start
if constexpr(turbine::config::ImGuiEnabled) {
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplGlfw_NewFrame();
ImGui::NewFrame();
}
for (auto d : gctx.drawers) {
d->draw(gctx);
}
if constexpr(turbine::config::ImGuiEnabled) {
ImGui::Render();
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
}
// draw end
glfwSwapBuffers(gctx.window);
}
static void draw(GLFWwindow *window, int, int) noexcept {
auto &gctx = *static_cast<GlfwContext*>(glfwGetWindowUserPointer(window));
draw(gctx);
}
ox::Result<ox::UPtr<Context>> init(ox::UPtr<ox::FileSystem> fs, ox::CRStringView appName) noexcept {
auto ctx = ox::make_unique<GlfwContext>();
oxReturnError(keel::init(ctx->keelCtx, std::move(fs), appName));
using namespace std::chrono;
ctx->startTime = duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count();
glfwInit();
oxReturnError(initGfx(*ctx));
glfwSetWindowSizeCallback(ctx->window, draw);
return ox::UPtr<Context>(ctx.release());
}
static void tickFps(GlfwContext &gctx, uint64_t nowMs) noexcept {
++gctx.draws;
if (gctx.draws >= 500) {
const auto duration = static_cast<double>(nowMs - gctx.prevFpsCheckTime) / 1000.0;
const auto fps = static_cast<int>(static_cast<double>(gctx.draws) / duration);
if constexpr(config::GlFpsPrint) {
oxOutf("FPS: {}\n", fps);
}
oxTracef("turbine.fps", "FPS: {}", fps);
gctx.prevFpsCheckTime = nowMs;
gctx.draws = 0;
}
}
ox::Error run(Context &ctx) noexcept {
auto &gctx = static_cast<GlfwContext&>(ctx);
int sleepTime = 0;
while (!glfwWindowShouldClose(gctx.window)) {
glfwPollEvents();
const auto ticks = ticksMs(gctx);
if (gctx.wakeupTime <= ticks) {
sleepTime = gctx.updateHandler(gctx);
if (sleepTime >= 0) {
gctx.wakeupTime = ticks + static_cast<unsigned>(sleepTime);
} else {
gctx.wakeupTime = ~uint64_t(0);
}
} else {
sleepTime = 10;
}
tickFps(gctx, ticks);
draw(gctx);
if (!gctx.constantRefresh) {
if (gctx.uninterruptedRefreshes) {
--gctx.uninterruptedRefreshes;
} else {
glfwWaitEventsTimeout(sleepTime);
}
}
}
shutdown(gctx);
return {};
}
void shutdown(Context &ctx) noexcept {
auto &gctx = static_cast<GlfwContext&>(ctx);
if (gctx.window) {
ImGui_ImplOpenGL3_Shutdown();
ImGui_ImplGlfw_Shutdown();
glfwDestroyWindow(gctx.window);
gctx.window = nullptr;
}
}
uint64_t ticksMs(const Context &ctx) noexcept {
using namespace std::chrono;
auto &gctx = static_cast<const GlfwContext&>(ctx);
const auto now = duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count();
return static_cast<uint64_t>(now - gctx.startTime);
}
bool buttonDown(const Context &ctx, Key key) noexcept {
auto &gctx = static_cast<const GlfwContext&>(ctx);
return (gctx.keysDown >> static_cast<int>(key)) & 1;
}
void requestShutdown(Context &ctx) noexcept {
auto &gctx = static_cast<GlfwContext&>(ctx);
glfwSetWindowShouldClose(gctx.window, true);
}
}

View File

@ -0,0 +1,50 @@
/*
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#pragma once
#include <ox/claw/claw.hpp>
#include <ox/std/memory.hpp>
#include <ox/std/string.hpp>
#include "context.hpp"
namespace turbine {
class BaseClipboardObject {
public:
virtual ~BaseClipboardObject() noexcept = default;
[[nodiscard]]
virtual ox::String typeId() const noexcept = 0;
[[nodiscard]]
constexpr auto typeMatch(auto name, auto version) const noexcept {
return typeId() == ox::buildTypeId(name, version);
}
};
template<typename T>
class ClipboardObject: public BaseClipboardObject {
[[nodiscard]]
ox::String typeId() const noexcept final {
return ox::buildTypeId(T::TypeName, T::TypeVersion);
}
};
ox::String getClipboardText(Context &ctx) noexcept;
void setClipboardText(Context &ctx, ox::CRStringView text) noexcept;
void setClipboardObject(Context &ctx, ox::UniquePtr<BaseClipboardObject> &&obj) noexcept;
ox::Result<BaseClipboardObject*> getClipboardData(Context &ctx, ox::StringView typeName, int typeVersion) noexcept;
template<typename T>
ox::Result<T*> getClipboardObject(Context &ctx) noexcept {
oxRequire(p, getClipboardData(ctx, T::TypeName, T::TypeVersion));
return dynamic_cast<T*>(p);
}
}

View File

@ -0,0 +1,56 @@
/*
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#pragma once
#include <ox/fs/fs.hpp>
#include <ox/model/desctypes.hpp>
#include <ox/std/buffer.hpp>
#include <ox/std/size.hpp>
#include <keel/context.hpp>
#include "input.hpp"
namespace turbine {
class Context;
struct ContextDeleter {
void operator()(Context *p) noexcept;
};
using ContextUPtr = ox::UPtr<Context, ContextDeleter>;
void shutdown(Context &ctx) noexcept;
keel::Context const&keelCtx(Context const&ctx) noexcept;
keel::Context &keelCtx(Context &ctx) noexcept;
inline ox::FileSystem const*rom(Context const&ctx) noexcept {
return keelCtx(ctx).rom.get();
}
inline ox::FileSystem *rom(Context &ctx) noexcept {
return keelCtx(ctx).rom.get();
}
void setApplicationData(Context &ctx, void *applicationData) noexcept;
[[nodiscard]]
void *applicationDataRaw(Context &ctx) noexcept;
template<typename T>
[[nodiscard]]
T *applicationData(Context &ctx) noexcept {
return static_cast<T*>(applicationDataRaw(ctx));
}
void setKeyEventHandler(Context &ctx, KeyEventHandler h) noexcept;
KeyEventHandler keyEventHandler(Context &ctx) noexcept;
}

View File

@ -41,6 +41,8 @@ ox::Size getScreenSize(Context &ctx) noexcept;
ox::Bounds getWindowBounds(Context &ctx) noexcept;
ox::Error setWindowBounds(Context &ctx, const ox::Bounds &bnds) noexcept;
ox::Error setWindowBounds(Context &ctx, ox::Bounds const&bnds) noexcept;
void setConstantRefresh(Context &ctx, bool r) noexcept;
}

View File

@ -72,7 +72,7 @@ enum Key {
class Context;
[[nodiscard]]
bool buttonDown(const Context &ctx, Key) noexcept;
bool buttonDown(Context const&ctx, Key) noexcept;
using KeyEventHandler = void(*)(Context&, Key, bool);

View File

@ -4,6 +4,7 @@
#pragma once
#include <ox/std/memory.hpp>
#include <ox/fs/fs.hpp>
#include "clipboard.hpp"
@ -13,16 +14,14 @@
namespace turbine {
ox::Result<ox::UniquePtr<Context>> init(ox::UniquePtr<ox::FileSystem> fs, ox::CRStringView appName) noexcept;
void shutdown(Context &ctx) noexcept;
ox::Result<ContextUPtr> init(ox::UPtr<ox::FileSystem> &&fs, ox::CRStringView appName) noexcept;
ox::Error run(Context &ctx) noexcept;
// Returns the number of milliseconds that have passed since the start of the
// program.
[[nodiscard]]
uint64_t ticksMs(const Context &ctx) noexcept;
uint64_t ticksMs(Context const&ctx) noexcept;
void requestShutdown(Context &ctx) noexcept;

View File

@ -0,0 +1,40 @@
add_library(Turbine)
set(TURBINE_BACKEND_GBA ${TURBINE_BUILD_TYPE} STREQUAL "GBA")
set(TURBINE_BACKEND_GLFW NOT ${TURBINE_BACKEND_GBA})
add_subdirectory(gba)
if(${TURBINE_BACKEND_GLFW})
add_subdirectory(glfw)
endif()
target_include_directories(
Turbine PUBLIC
../include
)
target_link_libraries(
Turbine PUBLIC
Keel
)
target_compile_definitions(
Turbine PRIVATE
TURBINE_BACKEND_GBA=$<IF:$<BOOL:${TURBINE_BACKEND_GBA}>,1,0>
TURBINE_BACKEND_GLFW=$<IF:$<BOOL:${TURBINE_BACKEND_GLFW}>,1,0>
)
install(
DIRECTORY
../include/turbine
DESTINATION
include/turbine
)
install(
TARGETS
Turbine
DESTINATION
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
)

View File

@ -1,13 +1,24 @@
option(TURBINE_GBA_EVENT_LOOP_TIMER_BASED "Run event loop on time instead of vsync" OFF)
set(TURBINE_GBA_TIMER_BITS "32" CACHE STRING "Bits for system time (16, 32, 64)")
add_library(Turbine-GBA OBJECT)
target_sources(
Turbine-GBA PRIVATE
context.cpp
clipboard.cpp
event.cpp
gfx.cpp
irq.arm.cpp
turbine.arm.cpp
turbine.cpp
)
target_compile_definitions(
Turbine-GBA PRIVATE
TURBINE_GBA_EVENT_LOOP_TIMER_BASED=$<IF:$<BOOL:${TURBINE_GBA_EVENT_LOOP_TIMER_BASED}>,true,false>
TURBINE_GBA_TIMER_BITS=${TURBINE_GBA_TIMER_BITS}
)
if(TURBINE_BUILD_TYPE STREQUAL "GBA")
enable_language(ASM)
set_source_files_properties(turbine.arm.cpp irq.arm.cpp PROPERTIES COMPILE_FLAGS -marm)
@ -23,6 +34,11 @@ else()
)
endif()
target_include_directories(
Turbine-GBA PUBLIC
../../include
)
target_link_libraries(
Turbine-GBA PUBLIC
TeaGBA

View File

@ -13,7 +13,6 @@ ox::String getClipboardText(Context&) noexcept {
}
void setClipboardText(Context&, ox::CRStringView) noexcept {
}
}

View File

@ -0,0 +1,12 @@
/*
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#pragma once
namespace turbine::config {
constexpr bool GbaEventLoopTimerBased = TURBINE_GBA_EVENT_LOOP_TIMER_BASED;
constexpr int GbaTimerBits = TURBINE_GBA_TIMER_BITS;
}

View File

@ -0,0 +1,29 @@
/*
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#include "context.hpp"
namespace turbine {
void ContextDeleter::operator()(Context *p) noexcept {
ox::safeDelete(p);
}
keel::Context const&keelCtx(Context const&ctx) noexcept {
return ctx.keelCtx;
}
keel::Context &keelCtx(Context &ctx) noexcept {
return ctx.keelCtx;
}
void setApplicationData(Context &ctx, void *applicationData) noexcept {
ctx.applicationData = applicationData;
}
void *applicationDataRaw(Context &ctx) noexcept {
return ctx.applicationData;
}
}

View File

@ -0,0 +1,36 @@
/*
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#pragma once
#include <keel/context.hpp>
#include <turbine/clipboard.hpp>
#include <turbine/context.hpp>
#include <turbine/event.hpp>
namespace turbine {
class Context {
public:
UpdateHandler updateHandler = [](Context&) -> int {return 0;};
keel::Context keelCtx;
KeyEventHandler keyEventHandler = nullptr;
void *applicationData = nullptr;
// GBA impl data /////////////////////////////////////////////////////////
bool running = true;
Context() noexcept = default;
Context(Context &other) noexcept = delete;
Context(Context const&other) noexcept = delete;
Context(Context const&&other) noexcept = delete;
virtual inline ~Context() noexcept {
shutdown(*this);
}
};
}

View File

@ -2,8 +2,9 @@
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#include <turbine/event.hpp>
#include "context.hpp"
#include "event.hpp"
namespace turbine {
@ -11,4 +12,12 @@ void setUpdateHandler(Context &ctx, UpdateHandler h) noexcept {
ctx.updateHandler = h;
}
}
void setKeyEventHandler(Context &ctx, KeyEventHandler h) noexcept {
ctx.keyEventHandler = h;
}
KeyEventHandler keyEventHandler(Context &ctx) noexcept {
return ctx.keyEventHandler;
}
}

View File

@ -43,7 +43,7 @@ ox::Bounds getWindowBounds(Context&) noexcept {
return {0, 0, 240, 160};
}
ox::Error setWindowBounds(Context&, const ox::Bounds&) noexcept {
ox::Error setWindowBounds(Context&, ox::Bounds const&) noexcept {
return OxError(1, "setWindowBounds not supported on GBA");
}

View File

@ -16,8 +16,7 @@ static gba_timer_t g_wakeupTime{};
ox::Error run(Context &ctx) noexcept {
g_wakeupTime = 0;
const auto gbaCtx = static_cast<gba::Context*>(&ctx);
while (gbaCtx->running) {
while (ctx.running) {
if (g_wakeupTime <= g_timerMs && ctx.updateHandler) {
auto sleepTime = ctx.updateHandler(ctx);
if (sleepTime >= 0) {

View File

@ -6,6 +6,7 @@
#include <teagba/irq.hpp>
#include <keel/keel.hpp>
#include <turbine/context.hpp>
#include <turbine/gfx.hpp>
#include "context.hpp"
@ -46,16 +47,17 @@ static ox::Result<std::size_t> findPreloadSection() noexcept {
constexpr auto headerP2Len = ox_strlen(headerP1);
constexpr auto headerLen = headerP1Len + headerP2Len;
for (auto current = MEM_ROM; current < reinterpret_cast<char*>(0x0a000000); current += headerLen) {
if (ox_memcmp(current, headerP1, headerP1Len) == 0 &&
ox_memcmp(current + headerP1Len, headerP2, headerP2Len) == 0) {
if (memcmp(current, headerP1, headerP1Len) == 0 &&
memcmp(current + headerP1Len, headerP2, headerP2Len) == 0) {
return reinterpret_cast<std::size_t>(current + headerLen);
}
}
return OxError(1);
}
ox::Result<ox::UniquePtr<turbine::Context>> init(ox::UPtr<ox::FileSystem> fs, ox::CRStringView appName) noexcept {
auto ctx = ox::make_unique<gba::Context>();
ox::Result<ContextUPtr> init(
ox::UPtr<ox::FileSystem> &&fs, ox::CRStringView appName) noexcept {
auto ctx = ox::make_unique<Context>();
oxReturnError(keel::init(ctx->keelCtx, std::move(fs), appName));
#ifdef OX_BARE_METAL
oxReturnError(findPreloadSection().moveTo(&ctx->keelCtx.preloadSectionOffset));
@ -63,7 +65,7 @@ ox::Result<ox::UniquePtr<turbine::Context>> init(ox::UPtr<ox::FileSystem> fs, ox
oxReturnError(initGfx(*ctx));
initTimer();
initIrq();
return ox::UPtr<turbine::Context>(std::move(ctx));
return ox::UPtr<turbine::Context, ContextDeleter>(std::move(ctx));
}
void shutdown(Context&) noexcept {
@ -73,13 +75,12 @@ uint64_t ticksMs(Context&) noexcept {
return g_timerMs;
}
bool buttonDown(Context&, Key k) noexcept {
bool buttonDown(Context const&, Key k) noexcept {
return k <= Key::GamePad_L && !(REG_GAMEPAD & (1 << static_cast<int>(k)));
}
void requestShutdown(Context &ctx) noexcept {
const auto gbaCtx = static_cast<gba::Context*>(&ctx);
gbaCtx->running = false;
ctx.running = false;
}
}

View File

@ -6,7 +6,7 @@
#include <ox/std/types.hpp>
#include "../config.hpp"
#include "config.hpp"
namespace turbine {

View File

@ -0,0 +1,24 @@
option(TURBINE_USE_IMGUI "Include DearImGUI in build (GLFW only)" ON)
option(TURBINE_GL_FPS_PRINT "Print FPS to stdout" OFF)
target_sources(
Turbine PRIVATE
context.cpp
clipboard.cpp
event.cpp
gfx.cpp
turbine.cpp
)
target_compile_definitions(
Turbine PRIVATE
TURBINE_USE_IMGUI=$<IF:$<BOOL:${TURBINE_USE_IMGUI}>,1,0>
TURBINE_GL_FPS_PRINT=$<IF:$<BOOL:${TURBINE_GL_FPS_PRINT}>,true,false>
)
target_link_libraries(
Turbine PUBLIC
glad
glfw
imgui
)

View File

@ -0,0 +1,36 @@
/*
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#include <GLFW/glfw3.h>
#include <ox/std/string.hpp>
#include <turbine/turbine.hpp>
#include "context.hpp"
namespace turbine {
ox::String getClipboardText(Context &ctx) noexcept {
return ox::String(glfwGetClipboardString(ctx.window));
}
void setClipboardText(Context &ctx, ox::CRStringView text) noexcept {
auto cstr = ox_malloca(text.bytes() + 1, char);
ox_strncpy(cstr.get(), text.data(), text.bytes());
glfwSetClipboardString(ctx.window, cstr.get());
}
void setClipboardObject(Context &ctx, ox::UniquePtr<BaseClipboardObject> &&obj) noexcept {
ctx.clipboard = std::move(obj);
}
ox::Result<BaseClipboardObject*> getClipboardData(Context &ctx, ox::StringView typeName, int typeVersion) noexcept {
if (ctx.clipboard && ctx.clipboard->typeMatch(typeName, typeVersion)) {
return ctx.clipboard.get();
}
return OxError(1);
}
}

View File

@ -0,0 +1,11 @@
/*
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#pragma once
namespace turbine::config {
constexpr bool GlFpsPrint = TURBINE_GL_FPS_PRINT;
}

View File

@ -0,0 +1,31 @@
/*
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#include <turbine/context.hpp>
#include "context.hpp"
namespace turbine {
void ContextDeleter::operator()(Context *p) noexcept {
ox::safeDelete(p);
}
keel::Context const&keelCtx(Context const&ctx) noexcept {
return ctx.keelCtx;
}
keel::Context &keelCtx(Context &ctx) noexcept {
return ctx.keelCtx;
}
void setApplicationData(Context &ctx, void *applicationData) noexcept {
ctx.applicationData = applicationData;
}
void *applicationDataRaw(Context &ctx) noexcept {
return ctx.applicationData;
}
}

View File

@ -0,0 +1,41 @@
/*
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#pragma once
#include <turbine/clipboard.hpp>
#include <turbine/context.hpp>
#include <turbine/gfx.hpp>
#include <turbine/event.hpp>
namespace turbine {
class Context {
public:
UpdateHandler updateHandler = [](Context&) -> int {return 0;};
keel::Context keelCtx;
KeyEventHandler keyEventHandler = nullptr;
void *applicationData = nullptr;
// GLFW impl data ////////////////////////////////////////////////////////
int uninterruptedRefreshes = 3;
ox::UPtr<BaseClipboardObject> clipboard;
struct GLFWwindow *window = nullptr;
// sets screen refresh to constant instead of only on event
bool constantRefresh = true;
ox::Vector<gl::Drawer*, 5> drawers;
int64_t startTime = 0;
uint64_t wakeupTime = 0;
uint64_t keysDown = 0;
uint64_t prevFpsCheckTime = 0;
uint64_t draws = 0;
Context() noexcept = default;
Context(Context const&other) noexcept = delete;
Context(Context const&&other) noexcept = delete;
};
}

View File

@ -0,0 +1,23 @@
/*
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#include <turbine/event.hpp>
#include "context.hpp"
namespace turbine {
void setUpdateHandler(Context &ctx, UpdateHandler h) noexcept {
ctx.updateHandler = h;
}
void setKeyEventHandler(Context &ctx, KeyEventHandler h) noexcept {
ctx.keyEventHandler = h;
}
KeyEventHandler keyEventHandler(Context &ctx) noexcept {
return ctx.keyEventHandler;
}
}

View File

@ -4,38 +4,27 @@
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#if TURBINE_USE_IMGUI
#include <imgui_impl_glfw.h>
#include <imgui_impl_opengl3.h>
#endif
#include <ox/std/defines.hpp>
#include <turbine/config.hpp>
#include "context.hpp"
namespace turbine {
[[nodiscard]]
inline GlfwContext &glctx(Context &ctx) noexcept {
if constexpr(ox::defines::Debug) {
return dynamic_cast<GlfwContext&>(ctx);
} else {
return static_cast<GlfwContext&>(ctx);
}
}
namespace gl {
void addDrawer(Context &ctx, Drawer *cd) noexcept {
auto &gctx = glctx(ctx);
gctx.drawers.emplace_back(cd);
ctx.drawers.emplace_back(cd);
}
void removeDrawer(Context &ctx, Drawer *cd) noexcept {
auto &gctx = glctx(ctx);
for (auto i = 0u; i < gctx.drawers.size(); ++i) {
if (gctx.drawers[i] == cd) {
oxIgnoreError(gctx.drawers.erase(i));
for (auto i = 0u; i < ctx.drawers.size(); ++i) {
if (ctx.drawers[i] == cd) {
oxIgnoreError(ctx.drawers.erase(i));
break;
}
}
@ -47,15 +36,12 @@ static void handleGlfwError(int err, const char *desc) noexcept {
oxErrf("GLFW error ({}): {}\n", err, desc);
}
static auto setKeyDownStatus(GlfwContext *gctx, Key key, bool down) noexcept {
if (down) {
gctx->keysDown |= 1llu << static_cast<int>(key);
} else {
gctx->keysDown &= ~(1llu << static_cast<int>(key));
}
static auto setKeyDownStatus(Context &ctx, Key key, bool down) noexcept {
ctx.keysDown &= ~(1llu << static_cast<int>(key));
ctx.keysDown |= static_cast<uint64_t>(down) << static_cast<int>(key);
}
static void handleKeyPress(Context *ctx, int key, bool down) noexcept {
static void handleKeyPress(Context &ctx, int key, bool down) noexcept {
static constexpr auto keyMap = [] {
ox::Array<Key, GLFW_KEY_LAST> map = {};
for (auto i = 0u; i < 26; ++i) {
@ -73,12 +59,11 @@ static void handleKeyPress(Context *ctx, int key, bool down) noexcept {
map[GLFW_KEY_ESCAPE] = Key::Escape;
return map;
}();
const auto eventHandler = keyEventHandler(*ctx);
auto &gctx = glctx(*ctx);
const auto eventHandler = keyEventHandler(ctx);
const auto k = keyMap[static_cast<std::size_t>(key)];
setKeyDownStatus(&gctx, k, down);
setKeyDownStatus(ctx, k, down);
if (eventHandler) {
eventHandler(*ctx, k, down);
eventHandler(ctx, k, down);
}
//if constexpr(ox::defines::Debug) {
// switch (key) {
@ -96,20 +81,21 @@ static void handleGlfwCursorPosEvent(GLFWwindow*, double, double) noexcept {
}
static void handleGlfwMouseButtonEvent(GLFWwindow *window, int, int, int) noexcept {
const auto ctx = static_cast<GlfwContext*>(glfwGetWindowUserPointer(window));
const auto ctx = static_cast<Context*>(glfwGetWindowUserPointer(window));
ctx->uninterruptedRefreshes = 25;
}
static void handleGlfwKeyEvent(GLFWwindow *window, int key, int, int action, int) noexcept {
const auto ctx = static_cast<GlfwContext*>(glfwGetWindowUserPointer(window));
const auto ctx = static_cast<Context*>(glfwGetWindowUserPointer(window));
ctx->uninterruptedRefreshes = 25;
if (action == GLFW_PRESS) {
handleKeyPress(ctx, key, true);
handleKeyPress(*ctx, key, true);
} else if (action == GLFW_RELEASE) {
handleKeyPress(ctx, key, false);
handleKeyPress(*ctx, key, false);
}
}
#if TURBINE_USE_IMGUI
static void themeImgui() noexcept {
// Dark Ruda style by Raikiri from ImThemes
auto &style = ImGui::GetStyle();
@ -207,9 +193,9 @@ static void themeImgui() noexcept {
style.Colors[ImGuiCol_NavWindowingDimBg] = imVec4(0.800000011920929, 0.800000011920929, 0.800000011920929, 0.2000000029802322);
style.Colors[ImGuiCol_ModalWindowDimBg] = imVec4(0.800000011920929, 0.800000011920929, 0.800000011920929, 0.3499999940395355);
}
#endif
ox::Error initGfx(Context &ctx) noexcept {
auto &gctx = glctx(ctx);
glfwSetErrorCallback(handleGlfwError);
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
@ -218,86 +204,76 @@ ox::Error initGfx(Context &ctx) noexcept {
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE);
}
glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE);
auto cstr = ox_malloca(ctx.keelCtx.appName.bytes() + 1, char);
ox_strncpy(cstr.get(), ctx.keelCtx.appName.data(), ctx.keelCtx.appName.bytes());
constexpr auto Scale = 5;
gctx.window = glfwCreateWindow(240 * Scale, 160 * Scale, cstr.get(), nullptr, nullptr);
//gctx.window = glfwCreateWindow(868, 741, cstr.get(), nullptr, nullptr);
if (gctx.window == nullptr) {
ctx.window = glfwCreateWindow(240 * Scale, 160 * Scale, ctx.keelCtx.appName.c_str(), nullptr, nullptr);
//ctx.window = glfwCreateWindow(876, 743, ctx.keelCtx.appName.c_str(), nullptr, nullptr);
if (ctx.window == nullptr) {
return OxError(1, "Could not open GLFW window");
}
glfwSetCursorPosCallback(gctx.window, handleGlfwCursorPosEvent);
glfwSetMouseButtonCallback(gctx.window, handleGlfwMouseButtonEvent);
glfwSetKeyCallback(gctx.window, handleGlfwKeyEvent);
glfwSetWindowUserPointer(gctx.window, &ctx);
glfwMakeContextCurrent(gctx.window);
glfwSetCursorPosCallback(ctx.window, handleGlfwCursorPosEvent);
glfwSetMouseButtonCallback(ctx.window, handleGlfwMouseButtonEvent);
glfwSetKeyCallback(ctx.window, handleGlfwKeyEvent);
glfwSetWindowUserPointer(ctx.window, &ctx);
glfwMakeContextCurrent(ctx.window);
if (!gladLoadGLES2Loader(reinterpret_cast<GLADloadproc>(glfwGetProcAddress))) {
return OxError(2, "Could not init Glad");
}
if constexpr(config::ImGuiEnabled) {
IMGUI_CHECKVERSION();
ImGui::CreateContext();
auto &io = ImGui::GetIO();
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
//io.MouseDrawCursor = true;
ImGui_ImplGlfw_InitForOpenGL(gctx.window, true);
ImGui_ImplOpenGL3_Init();
themeImgui();
}
#if TURBINE_USE_IMGUI
IMGUI_CHECKVERSION();
ImGui::CreateContext();
auto &io = ImGui::GetIO();
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
//io.MouseDrawCursor = true;
ImGui_ImplGlfw_InitForOpenGL(ctx.window, true);
ImGui_ImplOpenGL3_Init();
themeImgui();
#endif
return {};
}
void setWindowTitle(Context &ctx, ox::CRStringView title) noexcept {
auto &gctx = glctx(ctx);
auto cstr = ox_malloca(title.bytes() + 1, char);
ox_strncpy(cstr.get(), title.data(), title.bytes());
glfwSetWindowTitle(gctx.window, cstr.get());
glfwSetWindowTitle(ctx.window, cstr.get());
}
void focusWindow(Context &ctx) noexcept {
auto &gctx = glctx(ctx);
glfwFocusWindow(gctx.window);
glfwFocusWindow(ctx.window);
}
int getScreenWidth(Context &ctx) noexcept {
auto &gctx = glctx(ctx);
int w = 0, h = 0;
glfwGetFramebufferSize(gctx.window, &w, &h);
glfwGetFramebufferSize(ctx.window, &w, &h);
return w;
}
int getScreenHeight(Context &ctx) noexcept {
auto &gctx = glctx(ctx);
int w = 0, h = 0;
glfwGetFramebufferSize(gctx.window, &w, &h);
glfwGetFramebufferSize(ctx.window, &w, &h);
return h;
}
ox::Size getScreenSize(Context &ctx) noexcept {
auto &gctx = glctx(ctx);
int w = 0, h = 0;
glfwGetFramebufferSize(gctx.window, &w, &h);
glfwGetFramebufferSize(ctx.window, &w, &h);
return {w, h};
}
ox::Bounds getWindowBounds(Context &ctx) noexcept {
auto &gctx = glctx(ctx);
ox::Bounds bnds;
glfwGetWindowPos(gctx.window, &bnds.x, &bnds.y);
glfwGetWindowSize(gctx.window, &bnds.width, &bnds.height);
glfwGetWindowPos(ctx.window, &bnds.x, &bnds.y);
glfwGetWindowSize(ctx.window, &bnds.width, &bnds.height);
return bnds;
}
ox::Error setWindowBounds(Context &ctx, const ox::Bounds &bnds) noexcept {
auto &gctx = glctx(ctx);
glfwSetWindowPos(gctx.window, bnds.x, bnds.y);
glfwSetWindowSize(gctx.window, bnds.width, bnds.height);
ox::Error setWindowBounds(Context &ctx, ox::Bounds const&bnds) noexcept {
glfwSetWindowPos(ctx.window, bnds.x, bnds.y);
glfwSetWindowSize(ctx.window, bnds.width, bnds.height);
return {};
}
void setConstantRefresh(Context &ctx, bool r) noexcept {
auto &gctx = glctx(ctx);
gctx.constantRefresh = r;
ctx.constantRefresh = r;
}
}

View File

@ -0,0 +1,123 @@
/*
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#include <GLFW/glfw3.h>
#if TURBINE_USE_IMGUI
#include <imgui_impl_opengl3.h>
#include <imgui_impl_glfw.h>
#endif
#include <keel/keel.hpp>
#include <turbine/turbine.hpp>
#include "config.hpp"
#include "context.hpp"
namespace turbine {
static void draw(Context &ctx) noexcept {
// draw start
#if TURBINE_USE_IMGUI
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplGlfw_NewFrame();
ImGui::NewFrame();
#endif
for (auto d : ctx.drawers) {
d->draw(ctx);
}
#if TURBINE_USE_IMGUI
ImGui::Render();
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
#endif
// draw end
glfwSwapBuffers(ctx.window);
}
static void draw(GLFWwindow *window, int, int) noexcept {
auto &ctx = *static_cast<Context*>(glfwGetWindowUserPointer(window));
draw(ctx);
}
ox::Result<ContextUPtr> init(
ox::UPtr<ox::FileSystem> &&fs, ox::CRStringView appName) noexcept {
auto ctx = ox::make_unique<Context>();
oxReturnError(keel::init(ctx->keelCtx, std::move(fs), appName));
using namespace std::chrono;
ctx->startTime = duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count();
glfwInit();
oxReturnError(initGfx(*ctx));
glfwSetWindowSizeCallback(ctx->window, draw);
return ox::UPtr<Context, ContextDeleter>(ctx.release());
}
static void tickFps(Context &ctx, uint64_t nowMs) noexcept {
++ctx.draws;
if (ctx.draws >= 500) {
const auto duration = static_cast<double>(nowMs - ctx.prevFpsCheckTime) / 1000.0;
const auto fps = static_cast<int>(static_cast<double>(ctx.draws) / duration);
if constexpr(config::GlFpsPrint) {
oxOutf("FPS: {}\n", fps);
}
oxTracef("turbine.fps", "FPS: {}", fps);
ctx.prevFpsCheckTime = nowMs;
ctx.draws = 0;
}
}
ox::Error run(Context &ctx) noexcept {
int sleepTime = 0;
while (!glfwWindowShouldClose(ctx.window)) {
glfwPollEvents();
const auto ticks = ticksMs(ctx);
if (ctx.wakeupTime <= ticks) {
sleepTime = ctx.updateHandler(ctx);
if (sleepTime >= 0) {
ctx.wakeupTime = ticks + static_cast<unsigned>(sleepTime);
} else {
ctx.wakeupTime = ~uint64_t(0);
}
} else {
sleepTime = 10;
}
tickFps(ctx, ticks);
draw(ctx);
if (!ctx.constantRefresh) {
if (ctx.uninterruptedRefreshes) {
--ctx.uninterruptedRefreshes;
} else {
glfwWaitEventsTimeout(sleepTime);
}
}
}
shutdown(ctx);
return {};
}
void shutdown(Context &ctx) noexcept {
if (ctx.window) {
#if TURBINE_USE_IMGUI
ImGui_ImplOpenGL3_Shutdown();
ImGui_ImplGlfw_Shutdown();
#endif
glfwDestroyWindow(ctx.window);
ctx.window = nullptr;
}
}
uint64_t ticksMs(Context const&ctx) noexcept {
using namespace std::chrono;
const auto now = duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count();
return static_cast<uint64_t>(now - ctx.startTime);
}
bool buttonDown(Context const&ctx, Key key) noexcept {
return (ctx.keysDown >> static_cast<int>(key)) & 1;
}
void requestShutdown(Context &ctx) noexcept {
glfwSetWindowShouldClose(ctx.window, true);
}
}