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.

568 lines
16 KiB

2 months ago
//
// 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;
}