[olympic] Move keel, turbine, and studio to olympic

This commit is contained in:
2023-12-11 22:48:08 -06:00
parent a60765b338
commit e2545a956b
96 changed files with 32 additions and 24 deletions

View File

@ -0,0 +1 @@
add_subdirectory(src)

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(ox::StringView name, int 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

@ -0,0 +1,17 @@
/*
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#pragma once
namespace turbine {
class Context;
using UpdateHandler = int(*)(Context&);
// Sets event handler that sleeps for the time given in the return value. The
// sleep time is a minimum of ~16 milliseconds.
void setUpdateHandler(Context &ctx, UpdateHandler) noexcept;
}

View File

@ -0,0 +1,48 @@
/*
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#pragma once
#include <ox/std/array.hpp>
#include <ox/std/point.hpp>
#include <ox/std/size.hpp>
#include <ox/std/types.hpp>
#include <ox/model/def.hpp>
#include "context.hpp"
namespace turbine {
namespace gl {
class Drawer {
public:
virtual ~Drawer() = default;
virtual void draw(Context&) noexcept = 0;
};
void addDrawer(Context &ctx, Drawer *cd) noexcept;
void removeDrawer(Context &ctx, Drawer *cd) noexcept;
}
ox::Error initGfx(Context &ctx) noexcept;
void setWindowTitle(Context &ctx, ox::CRStringView title) noexcept;
void focusWindow(Context &ctx) noexcept;
[[nodiscard]]
int getScreenWidth(Context &ctx) noexcept;
[[nodiscard]]
int getScreenHeight(Context &ctx) noexcept;
[[nodiscard]]
ox::Size getScreenSize(Context &ctx) noexcept;
ox::Bounds getWindowBounds(Context &ctx) noexcept;
ox::Error setWindowBounds(Context &ctx, ox::Bounds const&bnds) noexcept;
void setConstantRefresh(Context &ctx, bool r) noexcept;
}

View File

@ -0,0 +1,79 @@
/*
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#pragma once
#include <ox/std/defines.hpp>
namespace turbine {
enum Key {
// GBA implementation currently relies on GamePad entry order
GamePad_A = 0,
GamePad_B,
GamePad_Select,
GamePad_Start,
GamePad_Right,
GamePad_Left,
GamePad_Up,
GamePad_Down,
GamePad_R,
GamePad_L,
Num_0,
Num_1,
Num_2,
Num_3,
Num_4,
Num_5,
Num_6,
Num_7,
Num_8,
Num_9,
Alpha_A,
Alpha_B,
Alpha_C,
Alpha_D,
Alpha_E,
Alpha_F,
Alpha_G,
Alpha_H,
Alpha_I,
Alpha_J,
Alpha_K,
Alpha_L,
Alpha_M,
Alpha_N,
Alpha_O,
Alpha_P,
Alpha_Q,
Alpha_R,
Alpha_S,
Alpha_T,
Alpha_U,
Alpha_V,
Alpha_W,
Alpha_X,
Alpha_Y,
Alpha_Z,
Mod_Alt,
Mod_Ctrl,
Mod_Super,
Mod_Shift,
Escape,
End
};
class Context;
[[nodiscard]]
bool buttonDown(Context const&ctx, Key) noexcept;
using KeyEventHandler = void(*)(Context&, Key, bool);
}

View File

@ -0,0 +1,28 @@
/*
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#pragma once
#include <ox/std/memory.hpp>
#include <ox/fs/fs.hpp>
#include "clipboard.hpp"
#include "event.hpp"
#include "gfx.hpp"
#include "input.hpp"
namespace turbine {
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(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

@ -0,0 +1,46 @@
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)
target_sources(
Turbine-GBA PRIVATE
irq.s
)
target_link_libraries(Turbine PUBLIC Turbine-GBA)
else()
target_sources(
Turbine-GBA PRIVATE
irqstub.cpp
)
endif()
target_include_directories(
Turbine-GBA PUBLIC
../../include
)
target_link_libraries(
Turbine-GBA PUBLIC
TeaGBA
Keel
)

View File

@ -0,0 +1,18 @@
/*
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#include <ox/std/string.hpp>
#include <turbine/context.hpp>
namespace turbine {
ox::String getClipboardText(Context&) noexcept {
return {};
}
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

@ -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

@ -0,0 +1,50 @@
/*
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#include <ox/std/size.hpp>
#include <ox/std/stringview.hpp>
#include <teagba/addresses.hpp>
#include <teagba/gfx.hpp>
#include <teagba/irq.hpp>
#include <turbine/context.hpp>
namespace turbine {
ox::Error initGfx(Context&) noexcept {
REG_DISPCTL = teagba::DispCtl_Mode0
| teagba::DispCtl_SpriteMap1D
| teagba::DispCtl_Obj;
// tell display to trigger vblank interrupts
REG_DISPSTAT = REG_DISPSTAT | teagba::DispStat_irq_vblank;
// enable vblank interrupt
REG_IE = REG_IE | teagba::Int_vblank;
return {};
}
void setWindowTitle(Context&, ox::CRStringView) noexcept {
}
int getScreenWidth(Context&) noexcept {
return 240;
}
int getScreenHeight(Context&) noexcept {
return 160;
}
ox::Size getScreenSize(Context&) noexcept {
return {240, 160};
}
ox::Bounds getWindowBounds(Context&) noexcept {
return {0, 0, 240, 160};
}
ox::Error setWindowBounds(Context&, ox::Bounds const&) noexcept {
return OxError(1, "setWindowBounds not supported on GBA");
}
}

View File

@ -0,0 +1,36 @@
/*
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
// NOTE: this file is compiled as ARM and not THUMB, so don't but too much in
// here
#include <teagba/addresses.hpp>
#include <teagba/gfx.hpp>
#include <teagba/irq.hpp>
#include "turbine.hpp"
namespace turbine {
volatile gba_timer_t g_timerMs = 0;
}
using namespace turbine;
extern "C" {
void turbine_isr_vblank() noexcept {
teagba::applySpriteUpdates();
if constexpr(config::GbaEventLoopTimerBased) {
// disable vblank interrupt until it is needed again
REG_IE = REG_IE & ~teagba::Int_vblank;
}
}
void turbine_isr_timer0() noexcept {
g_timerMs = g_timerMs + 1;
}
}

View File

@ -0,0 +1,110 @@
//
// Copyright 2016 - 2023 gary@drinkingtea.net
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
.section .iwram, "ax", %progbits
.arm
.align
.extern turbine_isr_vblank
.extern turbine_isr_timer0
.equ REG_IFBIOS, 0x03007ff8
.equ REG_IE, 0x04000200
.equ REG_IF, 0x04000202
.equ REG_IME, 0x04000208
.equ Int_vblank, 1
.equ Int_hblank, 2
.equ Int_vcount, 4
.equ Int_timer0, 8
.equ Int_timer1, 16
.equ Int_timer2, 32
.equ Int_timer3, 64
.equ Int_serial, 128 // link cable
.equ Int_dma0, 256
.equ Int_dma1, 512
.equ Int_dma2, 1024
.equ Int_dma3, 2048
.equ Int_dma4, 4096
.equ Int_dma5, 8192
.equ Int_input, 16384 // gamepad
.equ Int_cart, 32768 // cartridge removed
.global turbine_isr
.type turbine_isr, %function
turbine_isr:
// read IE
ldr r0, =#REG_IE
ldrh r1, [r0] // r1 becomes IE value
ldrh r2, [r0, #2] // r2 becomes IF value
and r1, r1, r2 // r1 becomes IE & IF
// done with r2 as IF value
// Acknowledge IRQ in REG_IF
strh r1, [r0, #2]
ldr r0, =#REG_IFBIOS
// Acknowledge IRQ in REG_IFBIOS
ldr r2, [r0] // r2 becomes REG_IFBIOS value
orr r2, r2, r1
str r2, [r0]
// done with r2 as IFBIOS value
// done with r0 as REG_IFBIOS
////////////////////////////////////////////////////
// Interrupt Table Begin //
////////////////////////////////////////////////////
cmp r1, #Int_timer0
ldreq r0, =turbine_isr_timer0
beq isr_call_handler
cmp r1, #Int_vblank
ldreq r0, =turbine_isr_vblank
beq isr_call_handler
////////////////////////////////////////////////////
// Interrupt Table End //
////////////////////////////////////////////////////
bx lr
isr_call_handler:
// clear IME to disable interrupts
ldr r2, =#REG_IME
mov r1, #0
str r1, [r2]
// enter system mode
mrs r1, cpsr
bic r1, r1, #0xDF
orr r1, r1, #0x1F
msr cpsr, r1
push {lr}
ldr lr, =isr_restore
bx r0
isr_restore:
pop {lr}
// re-enter irq mode
mrs r0, cpsr
bic r0, r0, #0xDF
orr r0, r0, #0x92
msr cpsr, r0
// set IME to re-enable interrupts
ldr r2, =#REG_IME
mov r0, #1
str r0, [r2]
bx lr
// vim: ft=armv4

View File

@ -0,0 +1,8 @@
/*
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
// stub for building TeaGBA for PC targets, for purposes of not having to
// switch back and forth between builds when editing GBA files
extern "C" void turbine_isr() {}

View File

@ -0,0 +1,40 @@
/*
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#include <teagba/addresses.hpp>
#include <teagba/bios.hpp>
#include <teagba/irq.hpp>
#include "context.hpp"
#include "turbine.hpp"
namespace turbine {
extern volatile gba_timer_t g_timerMs;
static gba_timer_t g_wakeupTime{};
ox::Error run(Context &ctx) noexcept {
g_wakeupTime = 0;
while (ctx.running) {
if (g_wakeupTime <= g_timerMs && ctx.updateHandler) {
auto sleepTime = ctx.updateHandler(ctx);
if (sleepTime >= 0) {
g_wakeupTime = g_timerMs + static_cast<unsigned>(sleepTime);
} else {
g_wakeupTime = ~gba_timer_t(0);
}
}
if constexpr(config::GbaEventLoopTimerBased) {
// wait for timer interrupt
teagba_intrwait(
0,
teagba::Int_timer0 | teagba::Int_timer1 | teagba::Int_timer2 | teagba::Int_timer3);
} else {
teagba_vblankintrwait();
}
}
return {};
}
}

View File

@ -0,0 +1,86 @@
/*
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#include <teagba/addresses.hpp>
#include <teagba/irq.hpp>
#include <keel/keel.hpp>
#include <turbine/context.hpp>
#include <turbine/gfx.hpp>
#include "context.hpp"
#include "turbine.hpp"
extern "C" void turbine_isr();
namespace turbine {
// Timer Consts
constexpr int NanoSecond = 1'000'000'000;
constexpr int MilliSecond = 1000;
constexpr int TicksMs59ns =
65535 - static_cast<uint16_t>(static_cast<double>(NanoSecond / MilliSecond) / 59.59);
extern volatile gba_timer_t g_timerMs;
static void initIrq() noexcept {
REG_ISR = turbine_isr;
REG_IME = 1; // enable interrupts
}
static void initTimer() noexcept {
// make timer0 a ~1 millisecond timer
REG_TIMER0 = TicksMs59ns;
REG_TIMER0CTL = 0b11000000;
// enable interrupt for timer0
REG_IE = REG_IE | teagba::Int_timer0;
}
[[maybe_unused]]
static ox::Result<std::size_t> findPreloadSection() noexcept {
// put the header in the wrong order to prevent mistaking this code for the
// media section
constexpr auto headerP2 = "DER_____________";
constexpr auto headerP1 = "KEEL_PRELOAD_HEA";
constexpr auto headerP1Len = ox_strlen(headerP2);
constexpr auto headerP2Len = ox_strlen(headerP1);
constexpr auto headerLen = headerP1Len + headerP2Len;
for (auto current = MEM_ROM; current < reinterpret_cast<char*>(0x0a000000); current += headerLen) {
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<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));
#endif
oxReturnError(initGfx(*ctx));
initTimer();
initIrq();
return ox::UPtr<turbine::Context, ContextDeleter>(std::move(ctx));
}
void shutdown(Context&) noexcept {
}
uint64_t ticksMs(Context&) noexcept {
return g_timerMs;
}
bool buttonDown(Context const&, Key k) noexcept {
return k <= Key::GamePad_L && !(REG_GAMEPAD & (1 << static_cast<int>(k)));
}
void requestShutdown(Context &ctx) noexcept {
ctx.running = false;
}
}

View File

@ -0,0 +1,15 @@
/*
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#pragma once
#include <ox/std/types.hpp>
#include "config.hpp"
namespace turbine {
using gba_timer_t = ox::Uint<config::GbaTimerBits>;
}

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

@ -0,0 +1,265 @@
/*
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#if TURBINE_USE_IMGUI
#include <imgui_impl_glfw.h>
#include <imgui_impl_opengl3.h>
#endif
#include "context.hpp"
namespace turbine {
namespace gl {
void addDrawer(Context &ctx, Drawer *cd) noexcept {
ctx.drawers.emplace_back(cd);
}
void removeDrawer(Context &ctx, Drawer *cd) noexcept {
for (auto i = 0u; i < ctx.drawers.size(); ++i) {
if (ctx.drawers[i] == cd) {
oxIgnoreError(ctx.drawers.erase(i));
break;
}
}
}
}
static void handleGlfwError(int err, const char *desc) noexcept {
oxErrf("GLFW error ({}): {}\n", err, desc);
}
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 constexpr auto keyMap = [] {
ox::Array<Key, GLFW_KEY_LAST> map = {};
for (auto i = 0u; i < 26; ++i) {
map[GLFW_KEY_A + i] = static_cast<Key>(static_cast<unsigned>(Key::Alpha_A) + i);
}
for (auto i = 0u; i < 10; ++i) {
map[GLFW_KEY_0 + i] = static_cast<Key>(static_cast<unsigned>(Key::Num_0) + i);
}
map[GLFW_KEY_LEFT_ALT] = Key::Mod_Alt;
map[GLFW_KEY_RIGHT_ALT] = Key::Mod_Alt;
map[GLFW_KEY_LEFT_CONTROL] = Key::Mod_Ctrl;
map[GLFW_KEY_RIGHT_CONTROL] = Key::Mod_Ctrl;
map[GLFW_KEY_LEFT_SUPER] = Key::Mod_Super;
map[GLFW_KEY_RIGHT_SUPER] = Key::Mod_Super;
map[GLFW_KEY_ESCAPE] = Key::Escape;
return map;
}();
const auto eventHandler = keyEventHandler(ctx);
const auto k = keyMap[static_cast<std::size_t>(key)];
setKeyDownStatus(ctx, k, down);
if (eventHandler) {
eventHandler(ctx, k, down);
}
}
static void handleGlfwCursorPosEvent(GLFWwindow*, double, double) noexcept {
}
static void handleGlfwMouseButtonEvent(GLFWwindow *window, int, int, int) noexcept {
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<Context*>(glfwGetWindowUserPointer(window));
ctx->uninterruptedRefreshes = 25;
if (action == GLFW_PRESS) {
handleKeyPress(*ctx, key, true);
} else if (action == GLFW_RELEASE) {
handleKeyPress(*ctx, key, false);
}
}
#if TURBINE_USE_IMGUI
static void themeImgui() noexcept {
// Dark Ruda style by Raikiri from ImThemes
auto &style = ImGui::GetStyle();
style.Alpha = 1.0;
style.DisabledAlpha = 0.6000000238418579;
style.WindowPadding = ImVec2(8.0, 8.0);
style.WindowRounding = 0.0;
style.WindowBorderSize = 1.0;
style.WindowMinSize = ImVec2(32.0, 32.0);
style.WindowTitleAlign = ImVec2(0.0, 0.5);
style.WindowMenuButtonPosition = ImGuiDir_Left;
style.ChildRounding = 0.0;
style.ChildBorderSize = 1.0;
style.PopupRounding = 0.0;
style.PopupBorderSize = 1.0;
style.FramePadding = ImVec2(4.0, 3.0);
// custom value
style.FrameRounding = 3.0;
style.FrameBorderSize = 0.0;
style.ItemSpacing = ImVec2(8.0, 4.0);
style.ItemInnerSpacing = ImVec2(4.0, 4.0);
style.CellPadding = ImVec2(4.0, 2.0);
style.IndentSpacing = 21.0;
style.ColumnsMinSpacing = 6.0;
style.ScrollbarSize = 14.0;
style.ScrollbarRounding = 9.0;
style.GrabMinSize = 10.0;
style.GrabRounding = 4.0;
style.TabRounding = 4.0;
style.TabBorderSize = 0.0;
style.TabMinWidthForCloseButton = 0.0;
style.ColorButtonPosition = ImGuiDir_Right;
style.ButtonTextAlign = ImVec2(0.5, 0.5);
style.SelectableTextAlign = ImVec2(0.0, 0.0);
// colors
constexpr auto imVec4 = [](double r, double g, double b, double a) {
return ImVec4(
static_cast<float>(r),
static_cast<float>(g),
static_cast<float>(b),
static_cast<float>(a));
};
style.Colors[ImGuiCol_Text] = imVec4(0.9490196108818054, 0.95686274766922, 0.9764705896377563, 1.0);
style.Colors[ImGuiCol_TextDisabled] = imVec4(0.3568627536296844, 0.4196078479290009, 0.4666666686534882, 1.0);
style.Colors[ImGuiCol_WindowBg] = imVec4(0.1098039224743843, 0.1490196138620377, 0.168627455830574, 1.0);
style.Colors[ImGuiCol_ChildBg] = imVec4(0.1490196138620377, 0.1764705926179886, 0.2196078449487686, 1.0);
style.Colors[ImGuiCol_PopupBg] = imVec4(0.0784313753247261, 0.0784313753247261, 0.0784313753247261, 0.9399999976158142);
style.Colors[ImGuiCol_Border] = imVec4(0.0784313753247261, 0.09803921729326248, 0.1176470592617989, 1.0);
style.Colors[ImGuiCol_BorderShadow] = imVec4(0.0, 0.0, 0.0, 0.0);
style.Colors[ImGuiCol_FrameBg] = imVec4(0.2000000029802322, 0.2470588237047195, 0.2862745225429535, 1.0);
style.Colors[ImGuiCol_FrameBgHovered] = imVec4(0.1176470592617989, 0.2000000029802322, 0.2784313857555389, 1.0);
style.Colors[ImGuiCol_FrameBgActive] = imVec4(0.08627451211214066, 0.1176470592617989, 0.1372549086809158, 1.0);
style.Colors[ImGuiCol_TitleBg] = imVec4(0.08627451211214066, 0.1176470592617989, 0.1372549086809158, 0.6499999761581421);
style.Colors[ImGuiCol_TitleBgActive] = imVec4(0.0784313753247261, 0.09803921729326248, 0.1176470592617989, 1.0);
style.Colors[ImGuiCol_TitleBgCollapsed] = imVec4(0.0, 0.0, 0.0, 0.5099999904632568);
style.Colors[ImGuiCol_MenuBarBg] = imVec4(0.1490196138620377, 0.1764705926179886, 0.2196078449487686, 1.0);
style.Colors[ImGuiCol_ScrollbarBg] = imVec4(0.01960784383118153, 0.01960784383118153, 0.01960784383118153, 0.3899999856948853);
style.Colors[ImGuiCol_ScrollbarGrab] = imVec4(0.2000000029802322, 0.2470588237047195, 0.2862745225429535, 1.0);
style.Colors[ImGuiCol_ScrollbarGrabHovered] = imVec4(0.1764705926179886, 0.2196078449487686, 0.2470588237047195, 1.0);
style.Colors[ImGuiCol_ScrollbarGrabActive] = imVec4(0.08627451211214066, 0.2078431397676468, 0.3098039329051971, 1.0);
style.Colors[ImGuiCol_CheckMark] = imVec4(0.2784313857555389, 0.5568627715110779, 1.0, 1.0);
style.Colors[ImGuiCol_SliderGrab] = imVec4(0.2784313857555389, 0.5568627715110779, 1.0, 1.0);
style.Colors[ImGuiCol_SliderGrabActive] = imVec4(0.3686274588108063, 0.6078431606292725, 1.0, 1.0);
style.Colors[ImGuiCol_Button] = imVec4(0.2000000029802322, 0.2470588237047195, 0.2862745225429535, 1.0);
style.Colors[ImGuiCol_ButtonHovered] = imVec4(0.2784313857555389, 0.5568627715110779, 1.0, 1.0);
style.Colors[ImGuiCol_ButtonActive] = imVec4(0.05882352963089943, 0.529411792755127, 0.9764705896377563, 1.0);
// custom value
style.Colors[ImGuiCol_Header] = imVec4(0.4000000029802322, 0.4470588237047195, 0.4862745225429535, 0.550000011920929);
style.Colors[ImGuiCol_HeaderHovered] = imVec4(0.2588235437870026, 0.5882353186607361, 0.9764705896377563, 0.800000011920929);
style.Colors[ImGuiCol_HeaderActive] = imVec4(0.2588235437870026, 0.5882353186607361, 0.9764705896377563, 1.0);
style.Colors[ImGuiCol_Separator] = imVec4(0.2000000029802322, 0.2470588237047195, 0.2862745225429535, 1.0);
style.Colors[ImGuiCol_SeparatorHovered] = imVec4(0.09803921729326248, 0.4000000059604645, 0.7490196228027344, 0.7799999713897705);
style.Colors[ImGuiCol_SeparatorActive] = imVec4(0.09803921729326248, 0.4000000059604645, 0.7490196228027344, 1.0);
style.Colors[ImGuiCol_ResizeGrip] = imVec4(0.2588235437870026, 0.5882353186607361, 0.9764705896377563, 0.25);
style.Colors[ImGuiCol_ResizeGripHovered] = imVec4(0.2588235437870026, 0.5882353186607361, 0.9764705896377563, 0.6700000166893005);
style.Colors[ImGuiCol_ResizeGripActive] = imVec4(0.2588235437870026, 0.5882353186607361, 0.9764705896377563, 0.949999988079071);
style.Colors[ImGuiCol_Tab] = imVec4(0.1098039224743843, 0.1490196138620377, 0.168627455830574, 1.0);
style.Colors[ImGuiCol_TabHovered] = imVec4(0.2588235437870026, 0.5882353186607361, 0.9764705896377563, 0.800000011920929);
style.Colors[ImGuiCol_TabActive] = imVec4(0.2000000029802322, 0.2470588237047195, 0.2862745225429535, 1.0);
style.Colors[ImGuiCol_TabUnfocused] = imVec4(0.1098039224743843, 0.1490196138620377, 0.168627455830574, 1.0);
style.Colors[ImGuiCol_TabUnfocusedActive] = imVec4(0.1098039224743843, 0.1490196138620377, 0.168627455830574, 1.0);
style.Colors[ImGuiCol_PlotLines] = imVec4(0.6078431606292725, 0.6078431606292725, 0.6078431606292725, 1.0);
style.Colors[ImGuiCol_PlotLinesHovered] = imVec4(1.0, 0.4274509847164154, 0.3490196168422699, 1.0);
style.Colors[ImGuiCol_PlotHistogram] = imVec4(0.8980392217636108, 0.6980392336845398, 0.0, 1.0);
style.Colors[ImGuiCol_PlotHistogramHovered] = imVec4(1.0, 0.6000000238418579, 0.0, 1.0);
style.Colors[ImGuiCol_TableHeaderBg] = imVec4(0.1882352977991104, 0.1882352977991104, 0.2000000029802322, 1.0);
style.Colors[ImGuiCol_TableBorderStrong] = imVec4(0.3098039329051971, 0.3098039329051971, 0.3490196168422699, 1.0);
style.Colors[ImGuiCol_TableBorderLight] = imVec4(0.2274509817361832, 0.2274509817361832, 0.2470588237047195, 1.0);
style.Colors[ImGuiCol_TableRowBg] = imVec4(0.0, 0.0, 0.0, 0.0);
style.Colors[ImGuiCol_TableRowBgAlt] = imVec4(1.0, 1.0, 1.0, 0.05999999865889549);
style.Colors[ImGuiCol_TextSelectedBg] = imVec4(0.2588235437870026, 0.5882353186607361, 0.9764705896377563, 0.3499999940395355);
style.Colors[ImGuiCol_DragDropTarget] = imVec4(1.0, 1.0, 0.0, 0.8999999761581421);
style.Colors[ImGuiCol_NavHighlight] = imVec4(0.2588235437870026, 0.5882353186607361, 0.9764705896377563, 1.0);
style.Colors[ImGuiCol_NavWindowingHighlight] = imVec4(1.0, 1.0, 1.0, 0.699999988079071);
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 {
glfwSetErrorCallback(handleGlfwError);
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE);
glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE);
constexpr auto Scale = 5;
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(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 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 cstr = ox_malloca(title.bytes() + 1, char);
ox_strncpy(cstr.get(), title.data(), title.bytes());
glfwSetWindowTitle(ctx.window, cstr.get());
}
void focusWindow(Context &ctx) noexcept {
glfwFocusWindow(ctx.window);
}
int getScreenWidth(Context &ctx) noexcept {
int w = 0, h = 0;
glfwGetFramebufferSize(ctx.window, &w, &h);
return w;
}
int getScreenHeight(Context &ctx) noexcept {
int w = 0, h = 0;
glfwGetFramebufferSize(ctx.window, &w, &h);
return h;
}
ox::Size getScreenSize(Context &ctx) noexcept {
int w = 0, h = 0;
glfwGetFramebufferSize(ctx.window, &w, &h);
return {w, h};
}
ox::Bounds getWindowBounds(Context &ctx) noexcept {
ox::Bounds bnds;
glfwGetWindowPos(ctx.window, &bnds.x, &bnds.y);
glfwGetWindowSize(ctx.window, &bnds.width, &bnds.height);
return bnds;
}
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 {
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);
}
}