You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
567 lines
16 KiB
567 lines
16 KiB
// |
|
// Copyright (c) 2010-2011 Matthew Jack and Doug Binks |
|
// |
|
// This software is provided 'as-is', without any express or implied |
|
// warranty. In no event will the authors be held liable for any damages |
|
// arising from the use of this software. |
|
// Permission is granted to anyone to use this software for any purpose, |
|
// including commercial applications, and to alter it and redistribute it |
|
// freely, subject to the following restrictions: |
|
// 1. The origin of this software must not be misrepresented; you must not |
|
// claim that you wrote the original software. If you use this software |
|
// in a product, an acknowledgment in the product documentation would be |
|
// appreciated but is not required. |
|
// 2. Altered source versions must be plainly marked as such, and must not be |
|
// misrepresented as being the original software. |
|
// 3. This notice may not be removed or altered from any source distribution. |
|
|
|
#include "Game.h" |
|
|
|
// Remove windows.h define of GetObject which conflicts with EntitySystem GetObject |
|
#if defined _WINDOWS_ && defined GetObject |
|
#undef GetObject |
|
#endif |
|
|
|
#include "CompilerLogger.h" |
|
#include "InterfaceIds.h" |
|
#include "IObjectUtils.h" |
|
#include "Console.h" |
|
#include "Environment.h" |
|
#include "ICameraControl.h" |
|
#include "ILightingControl.h" |
|
#include "IGameManager.h" |
|
|
|
#include "../../Common/AUOrientation3D.inl" |
|
#include "../../RuntimeCompiler/AUArray.h" |
|
|
|
#include "../../Renderer/AURenMesh.h" |
|
#include "../../Renderer/AURenderContext.h" |
|
#include "../../RuntimeCompiler/BuildTool.h" |
|
#include "../../RuntimeCompiler/ICompilerLogger.h" |
|
#include "../../Systems/ILogSystem.h" |
|
#include "../../Systems/IEntitySystem.h" |
|
#include "../../Systems/ITimeSystem.h" |
|
#include "../../Systems/IUpdateable.h" |
|
#include "../../RuntimeObjectSystem/IObjectFactorySystem.h" |
|
#include "../../RuntimeObjectSystem/RuntimeObjectSystem.h" |
|
#include "../../Systems/IGUISystem.h" |
|
#include "../../Systems/SystemTable.h" |
|
#include "../../Systems/IAssetSystem.h" |
|
|
|
#include "../../Systems/LogSystem/RocketLogSystem/RocketLogSystem.h" |
|
|
|
|
|
#include <stdio.h> |
|
#ifdef _WIN32 |
|
#include <tchar.h> |
|
#include <conio.h> |
|
#endif |
|
#include <strstream> |
|
#include <vector> |
|
#include <algorithm> |
|
|
|
#include <Rocket/Core.h> |
|
#include <Rocket/Controls.h> |
|
#include <Rocket/Debugger.h> |
|
#include "../../Systems/RocketLibSystem/RocketLibSystem.h" |
|
#include "../../Systems/RocketLibSystem/Input.h" |
|
|
|
#include <GL/glfw.h> |
|
|
|
using FileSystemUtils::Path; |
|
|
|
|
|
void Game::EntityUpdateProtector::ProtectedFunc() |
|
{ |
|
for (size_t i=0; i<entities.Size(); ++i) |
|
{ |
|
IAUEntity* pEnt = pEntitySystem->Get(entities[i]); |
|
if (pEnt) // Safety check in case entity was deleted during this update by another object |
|
{ |
|
IAUUpdateable* pUpdateable = pEnt->GetUpdateable(); |
|
if (pUpdateable) |
|
{ |
|
// If dropped here after a runtime failure, your crash was likely |
|
// somewhere directly in the pUpdatable object's Update method |
|
pUpdateable->Update(fDeltaTime); |
|
} |
|
} |
|
} |
|
} |
|
|
|
|
|
// Global pointer to Game object necessary so we can do callback to Game::MainLoop method |
|
// Could be dangerous if we're instantiating multiple Game objects for some reason |
|
static Game* g_pGame = NULL; |
|
void MainLoop_Wrapper() |
|
{ |
|
g_pGame->MainLoop(); |
|
} |
|
|
|
|
|
Game::Game() |
|
: m_pEnv(0) |
|
, m_pConsole(0) |
|
, m_pRocketContext(0) |
|
, m_pOpenGLRenderer(0) |
|
, m_pSystemInterface(0) |
|
, m_pRenderContext(0) |
|
, m_bRenderError(false) |
|
, m_pCameraControl(0) |
|
, m_pLightingControl(0) |
|
, m_fLastUpdateSessionTime(-1) |
|
, m_GameSpeed(1.0f) |
|
, m_CompileStartedTime(0.0) |
|
{ |
|
AU_ASSERT(g_pGame == NULL); |
|
g_pGame = this; |
|
} |
|
|
|
Game::~Game() |
|
{ |
|
delete m_pRenderContext; |
|
delete m_pSystemInterface; |
|
delete m_pOpenGLRenderer; |
|
delete m_pConsole; |
|
delete m_pEnv; |
|
} |
|
|
|
|
|
bool Game::Init() |
|
{ |
|
#ifdef _WIN32 |
|
// We Set Dir here so logs go to bin directory, useful for debugging as dir can be set anywhere. |
|
DWORD size = MAX_PATH; |
|
char filename[MAX_PATH]; |
|
GetModuleFileNameA( NULL, filename, size ); |
|
std::string strTempFileName( filename ); |
|
Path launchPath( strTempFileName ); |
|
launchPath = launchPath.ParentPath(); |
|
SetCurrentDirectoryA( launchPath.m_string.c_str() ); |
|
#endif |
|
|
|
|
|
m_pEnv = new Environment( this ); |
|
m_pSystemInterface = new RocketLibSystemSystemInterface(); |
|
m_pOpenGLRenderer = new RocketLibSystemRenderInterfaceOpenGL(); |
|
|
|
// Should be nearly zero, but cleaner to explicitly fetch it |
|
m_fLastUpdateSessionTime = m_pEnv->sys->pTimeSystem->GetSessionTimeNow(); |
|
|
|
RocketLibInit(); |
|
|
|
//AURenderContext must be initialized after RocketLibInit() due to OGL init in RocketLibInit; |
|
m_pRenderContext = new AURenderContext(); |
|
|
|
m_pEnv->sys->pObjectFactorySystem->AddListener(this); |
|
|
|
m_EntityUpdateProtector.pEntitySystem = m_pEnv->sys->pEntitySystem; |
|
|
|
m_pEnv->Init(); |
|
m_pConsole = new Console(m_pEnv, m_pRocketContext); |
|
|
|
return true; |
|
} |
|
|
|
|
|
void Game::Run() |
|
{ |
|
InitObjects(); |
|
|
|
RocketLibSystem::EventLoop(MainLoop_Wrapper); |
|
} |
|
|
|
|
|
void Game::Shutdown() |
|
{ |
|
// clean up any temp object files |
|
if( m_pEnv->sys->pRuntimeObjectSystem ) |
|
{ |
|
m_pEnv->sys->pRuntimeObjectSystem->CleanObjectFiles(); |
|
} |
|
|
|
DeleteObjects(); |
|
RocketLibShutdown(); |
|
} |
|
|
|
void Game::OnConstructorsAdded() |
|
{ |
|
InitStoredObjectPointers(); |
|
} |
|
|
|
void Game::Reset() |
|
{ |
|
ResetGame(); |
|
} |
|
|
|
void Game::Restart() |
|
{ |
|
delete IObjectUtils::GetUniqueObject( "MainObject" ); |
|
IObjectUtils::CreateUniqueObject( "MainObject" ); |
|
InitStoredObjectPointers(); |
|
} |
|
|
|
void Game::ToggleConsoleGUI() |
|
{ |
|
m_pConsole->ToggleGUI(); |
|
} |
|
|
|
void Game::Exit() |
|
{ |
|
RocketLibSystem::RequestExit(); |
|
} |
|
|
|
void Game::SetSpeed( float speed ) |
|
{ |
|
m_GameSpeed = speed; |
|
} |
|
|
|
void Game::GetWindowSize( float& width, float& height ) const |
|
{ |
|
int WindowSize[4]; |
|
RocketLibSystem::GetViewport( WindowSize ); |
|
width = (float)WindowSize[2]; |
|
height = (float)WindowSize[3]; |
|
} |
|
|
|
|
|
|
|
void Game::MainLoop() |
|
{ |
|
ITimeSystem *pTimeSystem = m_pEnv->sys->pTimeSystem; |
|
|
|
// Time in userspace, ignoring frametime and whether we are paused, compiling, etc. |
|
// That seems most appropriate to the filechangenotifier |
|
double fSessionTimeNow = pTimeSystem->GetSessionTimeNow(); |
|
float fSessionTimeDelta = (float)(fSessionTimeNow - m_fLastUpdateSessionTime); |
|
float fClampedDelta = (std::min)( fSessionTimeDelta*m_GameSpeed, 0.1f ); // used for IObject updates |
|
m_fLastUpdateSessionTime = fSessionTimeNow; |
|
|
|
m_pEnv->sys->pFileChangeNotifier->Update(fSessionTimeDelta); |
|
|
|
if( m_pEnv->sys->pRuntimeObjectSystem->GetIsCompiling() && m_CompileStartedTime == 0.0 ) |
|
{ |
|
m_CompileStartedTime = pTimeSystem->GetSessionTimeNow(); |
|
} |
|
|
|
//check status of any compile |
|
bool bLoadModule = false; |
|
if( m_pEnv->sys->pRuntimeObjectSystem->GetIsCompiledComplete() ) |
|
{ |
|
bLoadModule = true; //we load module after update/display, to get notification on screen correct |
|
} |
|
|
|
pTimeSystem->StartFrame(); |
|
|
|
AUDynArray<AUEntityId> entities; |
|
m_EntityUpdateProtector.pEntitySystem->GetAll(m_EntityUpdateProtector.entities); |
|
m_EntityUpdateProtector.fDeltaTime = fClampedDelta; |
|
|
|
if (!m_pEnv->sys->pRuntimeObjectSystem->TryProtectedFunction( &m_EntityUpdateProtector ) ) |
|
{ |
|
m_pEnv->sys->pLogSystem->Log(eLV_ERRORS, "Have caught an exception in main entity Update loop, code will not be run until new compile - please fix.\n"); |
|
} |
|
|
|
|
|
if (!m_pCameraControl || !m_pLightingControl) |
|
{ |
|
if (!m_bRenderError) |
|
{ |
|
m_bRenderError = true; |
|
m_pEnv->sys->pLogSystem->Log(eLV_ERRORS, "Missing Camera and/or Lighting control. Can't render world.\n"); |
|
} |
|
} |
|
else |
|
{ |
|
m_bRenderError = false; |
|
RenderWorld(); |
|
} |
|
|
|
RocketLibUpdate(); |
|
|
|
if( bLoadModule ) |
|
{ |
|
// load module when compile complete, and notify console - TODO replace with event system |
|
bool bSuccess = m_pEnv->sys->pRuntimeObjectSystem->LoadCompiledModule(); |
|
m_pConsole->OnCompileDone(bSuccess); |
|
if( bSuccess ) |
|
{ |
|
float compileAndLoadTime = (float)( pTimeSystem->GetSessionTimeNow() - m_CompileStartedTime ); |
|
m_pEnv->sys->pLogSystem->Log(eLV_COMMENTS, "Compile and Module Reload Time: %.1f s\n", compileAndLoadTime); |
|
|
|
} |
|
m_CompileStartedTime = 0.0; |
|
} |
|
|
|
// Limit frame rate |
|
double dTimeTaken = pTimeSystem->GetFrameTimeNow(); |
|
const double dIdealTime = 1.0 / 70.0; //ideal time is actually 1/60, but we want some leeway |
|
if ( dTimeTaken < dIdealTime) |
|
{ |
|
glfwSleep( dIdealTime - dTimeTaken ); |
|
} |
|
|
|
pTimeSystem->EndFrame(); |
|
} |
|
|
|
void Game::RenderWorld() |
|
{ |
|
float params[4]; |
|
|
|
m_pLightingControl->GetBackColor(params); |
|
glClearColor(params[0], params[1], params[2], params[3]); |
|
glClearDepth( 1.0 ); |
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); |
|
|
|
// Hacked render of mesh |
|
glDisable(GL_FOG); |
|
|
|
int WindowSize[4]; |
|
RocketLibSystem::GetViewport( WindowSize ); |
|
//may need to re-size context, check |
|
Rocket::Core::Vector2i contextSize = m_pRocketContext->GetDimensions(); |
|
if( contextSize.x != WindowSize[2] || contextSize.y != WindowSize[3] ) |
|
{ |
|
contextSize.x = WindowSize[2]; |
|
contextSize.y = WindowSize[3]; |
|
m_pRocketContext->SetDimensions( contextSize ); |
|
} |
|
|
|
glMatrixMode(GL_PROJECTION); |
|
glLoadIdentity(); |
|
//gluPerspective(60.0f,(GLdouble)WindowSize[2]/(GLdouble)WindowSize[3], |
|
// 1.0f, 100000.0f); |
|
glOrtho( (GLdouble)contextSize.x * -0.5, (GLdouble)contextSize.x * 0.5, |
|
(GLdouble)contextSize.y * -0.5, (GLdouble)contextSize.y * 0.5, |
|
1.0f, 100000.0f ); |
|
glMatrixMode(GL_MODELVIEW); // Always go back to modelview matrix |
|
glLoadIdentity(); |
|
float fglMatrix[16]; |
|
AUOrientation3D viewOrientation; |
|
viewOrientation.Set( AUVec3f( 0.0f, 1.0f, 0.0f ), AUVec3f( 0.0f, 0.0f, 1.0f ) ); |
|
viewOrientation.LoadglViewMatrix(fglMatrix); |
|
glMultMatrixf(fglMatrix); |
|
AUVec3f viewPos = m_pCameraControl->GetCurrentPos(); |
|
glTranslatef( -viewPos.x, |
|
-viewPos.y, |
|
-viewPos.z ); |
|
|
|
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); |
|
glShadeModel(GL_SMOOTH); |
|
glDisable(GL_POLYGON_SMOOTH); |
|
glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER, 1); |
|
m_pLightingControl->GetGlobalAmbient(params); |
|
glLightModelfv(GL_LIGHT_MODEL_AMBIENT, params); |
|
glEnable(GL_LIGHTING); |
|
m_pLightingControl->GetLightAmbient(params); |
|
glLightfv(GL_LIGHT0, GL_AMBIENT, params); |
|
m_pLightingControl->GetLightDiffuse(params); |
|
glLightfv(GL_LIGHT0, GL_DIFFUSE, params); |
|
m_pLightingControl->GetLightSpecular(params); |
|
glLightfv(GL_LIGHT0, GL_SPECULAR, params); |
|
glEnable(GL_LIGHT0); |
|
glCullFace(GL_BACK); |
|
glEnable(GL_CULL_FACE); |
|
glDisable( GL_TEXTURE_2D); |
|
glDisableClientState(GL_COLOR_ARRAY); |
|
glDisable(GL_SCISSOR_TEST); |
|
glDisable(GL_TEXTURE_2D); |
|
glDisable(GL_BLEND); |
|
glDisable( GL_ALPHA_TEST ); |
|
glDisable(GL_STENCIL_TEST); |
|
glEnable(GL_DEPTH_TEST); |
|
glDepthFunc( GL_LESS ); |
|
|
|
m_pRenderContext->Render( m_pEnv->sys->pEntitySystem ); |
|
// End mesh draw |
|
|
|
} |
|
|
|
void Game::RocketLibUpdate() |
|
{ |
|
// Push through any log messages before rendering |
|
m_pEnv->sys->pRocketLogSystem->Push(); |
|
|
|
RocketLibSystem::PreRenderRocketLib(); |
|
m_pRocketContext->Update(); |
|
m_pRocketContext->Render(); |
|
|
|
RocketLibSystem::FlipBuffers(); |
|
} |
|
|
|
void Game::RocketLibInit() |
|
{ |
|
// Generic OS initialisation, creates a window and attaches OpenGL. |
|
if (!RocketLibSystem::Initialise() || |
|
!RocketLibSystem::OpenWindow("Pulse", true)) |
|
{ |
|
RocketLibSystem::Shutdown(); |
|
return; |
|
} |
|
|
|
// Rocket initialisation. |
|
Rocket::Core::SetRenderInterface(m_pOpenGLRenderer); |
|
Rocket::Core::SetSystemInterface(m_pSystemInterface); |
|
Rocket::Core::Initialise(); |
|
Rocket::Controls::Initialise(); |
|
|
|
// Create the main Rocket context and set it on the RocketLibSystem's input layer. |
|
Rocket::Core::Vector2i contextSize; |
|
int WindowSize[4]; |
|
RocketLibSystem::GetViewport( WindowSize ); |
|
contextSize.x = WindowSize[2]; |
|
contextSize.y = WindowSize[3]; |
|
m_pRocketContext = Rocket::Core::CreateContext("main", contextSize); |
|
if (m_pRocketContext == NULL) |
|
{ |
|
Rocket::Core::Shutdown(); |
|
RocketLibSystem::Shutdown(); |
|
return; |
|
} |
|
|
|
m_pEnv->sys->pGUISystem->SetContext(m_pRocketContext); |
|
|
|
Rocket::Debugger::Initialise(m_pRocketContext); |
|
Input::SetContext(m_pRocketContext); |
|
|
|
RocketLibSystem::LoadFonts("/GUI/"); |
|
|
|
// Set the Rocketlib logger font size |
|
IGUIElement* pElement = m_pEnv->sys->pGUISystem->GetLogElement(); |
|
if (pElement) |
|
{ |
|
pElement->SetProperty("font-size", "18pt"); |
|
pElement->RemoveReference(); |
|
} |
|
|
|
} |
|
|
|
|
|
void Game::RocketLibShutdown() |
|
{ |
|
// ensure any log messages prior to shutdown are output |
|
if( m_pEnv->sys->pRocketLogSystem ) |
|
{ |
|
m_pEnv->sys->pRocketLogSystem->Push(); |
|
} |
|
|
|
m_pRocketContext->RemoveReference(); |
|
Rocket::Core::Shutdown(); |
|
|
|
RocketLibSystem::CloseWindow(); |
|
RocketLibSystem::Shutdown(); |
|
} |
|
|
|
void Game::InitObjects() |
|
{ |
|
InitStoredObjectPointers(); |
|
} |
|
|
|
void Game::DeleteObjects() |
|
{ |
|
m_pConsole->DestroyContext(); |
|
|
|
delete IObjectUtils::GetUniqueObject( "MainObject" ); |
|
|
|
|
|
#ifdef _DEBUG |
|
// Do a check to verify that all objects have been destroyed at this point |
|
int totalObjectCount = 0; |
|
AUDynArray<IObjectConstructor*> constructors; |
|
m_pEnv->sys->pObjectFactorySystem->GetAll(constructors); |
|
for (size_t i=0; i<constructors.Size(); ++i) |
|
{ |
|
IObjectConstructor* pConstructor = constructors[i]; |
|
|
|
size_t count = pConstructor->GetNumberConstructedObjects(); |
|
|
|
// Need to iterate through all objects and check if they're valid |
|
// since GetNumConstructedObjects isn't accurate, can return some null pointers |
|
for (size_t j=0; j<count; ++j) |
|
{ |
|
if (pConstructor->GetConstructedObject(j) != NULL) |
|
{ |
|
totalObjectCount++; |
|
} |
|
} |
|
|
|
// Do an assert check here so it's easy to figure out which object type wasn't deleted |
|
AU_ASSERT( totalObjectCount == 0 ); |
|
} |
|
|
|
#endif |
|
} |
|
|
|
void Game::ResetGame() |
|
{ |
|
IGameManager* pGameManager = (IGameManager*)IObjectUtils::GetUniqueInterface( "GameManager", IID_IGAMEMANAGER ); |
|
pGameManager->ResetGame(); |
|
} |
|
|
|
void Game::InitStoredObjectPointers() |
|
{ |
|
m_pCameraControl = (ICameraControl*)IObjectUtils::GetUniqueInterface( "CameraControl", IID_ICAMERACONTROL ); |
|
AU_ASSERT(m_pCameraControl); |
|
|
|
m_pLightingControl = (ILightingControl*)IObjectUtils::GetUniqueInterface( "LightingControl", IID_ILIGHTINGCONTROL ); |
|
AU_ASSERT(m_pLightingControl); |
|
} |
|
|
|
void Game::RunRCCppTests( bool bTestFileTracking ) |
|
{ |
|
m_pEnv->sys->pRuntimeObjectSystem->CleanObjectFiles(); |
|
m_pEnv->sys->pRuntimeObjectSystem->TestBuildAllRuntimeSourceFiles( this, bTestFileTracking ); |
|
m_pEnv->sys->pRuntimeObjectSystem->TestBuildAllRuntimeHeaders( this, bTestFileTracking ); |
|
} |
|
|
|
|
|
bool Game::TestBuildCallback(const char* file, TestBuildResult type) |
|
{ |
|
switch( type ) |
|
{ |
|
case TESTBUILDRRESULT_SUCCESS: // SUCCESS, yay! |
|
m_pEnv->sys->pLogSystem->Log(eLV_EVENTS, "TESTBUILDRRESULT_SUCCESS: %s\n", file); |
|
break; |
|
case TESTBUILDRRESULT_NO_FILES_TO_BUILD: // file registration error or no runtime files of this type |
|
m_pEnv->sys->pLogSystem->Log(eLV_WARNINGS, "TESTBUILDRRESULT_NO_FILES_TO_BUILD\n"); |
|
break; |
|
case TESTBUILDRRESULT_BUILD_FILE_GONE: // the file is no longer present |
|
m_pEnv->sys->pLogSystem->Log(eLV_ERRORS, "TESTBUILDRRESULT_BUILD_FILE_GONE: %s\n", file); |
|
break; |
|
case TESTBUILDRRESULT_BUILD_NOT_STARTED: // file change detection could be broken, or if an include may not be included anywhere |
|
m_pEnv->sys->pLogSystem->Log(eLV_ERRORS, "TESTBUILDRRESULT_BUILD_NOT_STARTED: %s\n", file); |
|
break; |
|
case TESTBUILDRRESULT_BUILD_FAILED: // a build was started, but it failed or module failed to load. See log. |
|
m_pEnv->sys->pLogSystem->Log(eLV_ERRORS, "TESTBUILDRRESULT_BUILD_FAILED: %s\n", file); |
|
break; |
|
case TESTBUILDRRESULT_OBJECT_SWAP_FAIL: // build succeeded, module loaded but errors on swapping |
|
m_pEnv->sys->pLogSystem->Log(eLV_ERRORS, "TESTBUILDRRESULT_OBJECT_SWAP_FAIL: %s\n", file); |
|
break; |
|
default: |
|
assert(false); |
|
break; |
|
} |
|
return true; |
|
} |
|
|
|
bool Game::TestBuildWaitAndUpdate() |
|
{ |
|
double dTimeStart = glfwGetTime(); |
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); |
|
RocketLibUpdate(); |
|
double dTimeEnd = glfwGetTime(); |
|
|
|
const double dGoodWaitTime = 0.05; |
|
double dTimeLeft = dGoodWaitTime - (dTimeEnd - dTimeStart); |
|
if( dTimeLeft > 0.0 ) |
|
{ |
|
glfwSleep( dTimeLeft ); |
|
} |
|
|
|
if( !glfwGetWindowParam( GLFW_OPENED ) ) |
|
{ |
|
// closed window so stop |
|
return false; |
|
} |
|
return true; |
|
}
|
|
|