Commit 0e9818a7 authored by NTAuthority's avatar NTAuthority

proto-game loop - more to come

parent 09bcf351
Pipeline #45 skipped
#pragma once
namespace krt
{
namespace sys
{
// gets the current time in milliseconds
uint64_t Milliseconds();
// a timer precision context (say, timeBeginPeriod on Windows)
class TimerContext
{
public:
TimerContext();
~TimerContext();
};
}
}
\ No newline at end of file
......@@ -34,6 +34,8 @@ int Main::Run(const ProgramArguments& arguments)
{
Game TheGame;
TheGame.Run();
ConsoleCommand cmd("a", [] (int a, int b, int c)
{
printf("%d %d %d\n", a, b, c);
......
#include <StdInc.h>
#include <sys/Timer.h>
#include <windows.h>
#include <mmsystem.h>
#pragma comment(lib, "winmm.lib")
namespace krt
{
namespace sys
{
// following function is taken from the Grit Game Engine, the terms of which follow:
/* Copyright (c) David Cunningham and the Grit Game Engine project 2012
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
uint64_t Milliseconds()
{
static bool initialized = false;
static unsigned long long freq; // cpu cycles per microsecond
if (!initialized)
{
LARGE_INTEGER f;
QueryPerformanceFrequency(&f);
freq = f.QuadPart / 1000000ULL;
}
static unsigned long long last_tick_count_us;
static unsigned long long last_perf_us;
static unsigned long long adjust_ticks = 0;
unsigned long long perf_ticks;
{
LARGE_INTEGER perf_counter;
HANDLE curr_thread = GetCurrentThread();
DWORD_PTR old_mask = SetThreadAffinityMask(curr_thread, 0);
QueryPerformanceCounter(&perf_counter);
SetThreadAffinityMask(curr_thread, old_mask);
perf_ticks = perf_counter.QuadPart;
perf_ticks -= adjust_ticks;
}
unsigned long long this_perf_us = perf_ticks / freq;
unsigned long long this_tick_count_us = GetTickCount() * 1000;
if (initialized)
{
// http://support.microsoft.com/kb/274323
unsigned long long elapsed_ticks_us = this_tick_count_us - last_tick_count_us;
unsigned long long elapsed_perf_us = this_perf_us - last_perf_us;
int error_us = int(((long long)(elapsed_perf_us)) - ((long long)(elapsed_ticks_us)));
if (error_us > 500000)
{
unsigned long long adj_ticks = error_us*freq;
adjust_ticks += adj_ticks;
perf_ticks -= adj_ticks;
this_perf_us = perf_ticks / freq; // needs recomputing
}
// make sure adjustment doesn't make time run backwards
if ((long long)(this_perf_us - last_perf_us) < 0) this_perf_us = last_perf_us;
}
last_tick_count_us = this_tick_count_us;
last_perf_us = this_perf_us;
initialized = true;
return this_perf_us / 1000;
}
// increase scheduler precision
TimerContext::TimerContext()
{
timeBeginPeriod(1);
}
// reset scheduler precision
TimerContext::~TimerContext()
{
timeEndPeriod(1);
}
}
}
\ No newline at end of file
#pragma once
#include <queue>
namespace krt
{
class Event abstract
{
public:
inline uint64_t GetTime() { return m_time; }
inline void SetTime(uint64_t time) { m_time = time; }
public:
virtual void Handle() = 0;
private:
uint64_t m_time;
};
class EventSystem
{
public:
uint64_t HandleEvents();
void QueueEvent(std::unique_ptr<Event>&& ev);
private:
std::unique_ptr<Event> GetEvent();
private:
std::queue<std::unique_ptr<Event>> m_events;
std::mutex m_mutex;
};
}
\ No newline at end of file
......@@ -25,6 +25,8 @@ class Game
Game(void);
~Game(void);
void Run();
std::string GetGamePath(std::string relPath);
inline streaming::StreamMan& GetStreaming(void) { return this->streaming; }
......@@ -33,6 +35,10 @@ class Game
inline World* GetWorld(void) { return &theWorld; }
inline float GetDelta() { return dT; }
inline uint32_t GetLastFrameTime() { return lastFrameTime; }
std::string GetDevicePathPrefix(void) { return "gta3:/"; }
GameUniversePtr AddUniverse(const GameConfiguration& configuration);
......@@ -40,6 +46,9 @@ class Game
GameUniversePtr GetUniverse(const std::string& name);
private:
float dT;
uint32_t lastFrameTime;
std::string gameDir;
streaming::StreamMan streaming;
......
#include <StdInc.h>
#include <EventSystem.h>
#include <sys/Timer.h>
namespace krt
{
class NullEvent : public Event
{
public:
void Handle() override
{
// do nothing, this event isn't meant to be handled
}
};
uint64_t EventSystem::HandleEvents()
{
while (true)
{
std::unique_ptr<Event> event = GetEvent();
if (dynamic_cast<NullEvent*>(event.get()) != nullptr)
{
return event->GetTime();
}
event->Handle();
}
}
std::unique_ptr<Event> EventSystem::GetEvent()
{
std::lock_guard<std::mutex> lock(m_mutex);
if (!m_events.empty())
{
auto event = std::move(m_events.front());
m_events.pop();
return std::move(event);
}
// return the current time if we have no events anymore
{
auto event = std::make_unique<NullEvent>();
event->SetTime(sys::Milliseconds());
return std::move(event);
}
}
void EventSystem::QueueEvent(std::unique_ptr<Event>&& ev)
{
m_events.push(std::move(ev));
}
}
\ No newline at end of file
......@@ -11,6 +11,10 @@
#include "CdImageDevice.h"
#include "vfs/Manager.h"
#include "EventSystem.h"
#include "sys/Timer.h"
#include <src/rwgta.h>
#pragma warning(disable : 4996)
......@@ -71,7 +75,7 @@ Game::Game(void) : streaming(GAME_NUM_STREAMING_CHANNELS), texManager(streaming)
universe->Load();
// Do a test that loads all game models.
modelManager.LoadAllModels();
//modelManager.LoadAllModels();
}
Game::~Game(void)
......@@ -91,6 +95,64 @@ Game::~Game(void)
theGame = NULL;
}
void Game::Run()
{
sys::TimerContext timerContext;
EventSystem eventSystem;
// run the main game loop
bool wantsToExit = false;
uint64_t lastTime = 0;
while (!wantsToExit)
{
// limit frame rate and handle events
// TODO: non-busy wait?
int minMillis = (1000 / 60); // 60 is to be replaced by a 'max fps' console value
uint32_t millis = 0;
uint64_t thisTime = 0;
do
{
thisTime = eventSystem.HandleEvents();
millis = thisTime - lastTime;
} while (millis < minMillis);
// handle time scaling
float scale = 1.0f; // to be replaced by a console value, again
millis *= scale;
if (millis < 1)
{
millis = 1;
}
// prevent too big jumps from being made
else if (millis > 5000)
{
millis = 5000;
}
if (millis > 500)
{
printf("long frame: %d millis\n", millis);
}
// store timing values for this frame
this->dT = millis / 1000.0f;
this->lastFrameTime = millis;
lastTime = thisTime;
// execute the command buffer for the global console
console::ExecuteBuffer();
// whatever else might come to mind
}
}
GameUniversePtr Game::AddUniverse(const GameConfiguration& configuration)
{
auto universe = std::make_shared<GameUniverse>(configuration);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment