1
0
mirror of https://github.com/rwengine/openrw.git synced 2024-11-07 03:12:36 +01:00

Initial Script Machine implementation + some ops

This commit is contained in:
Daniel Evans 2014-07-23 23:57:21 +01:00
parent e0a0c4882f
commit c3e2172f3b
15 changed files with 455 additions and 54 deletions

View File

@ -18,6 +18,7 @@ struct DynamicObjectData;
struct WeaponData;
class GameWorld;
class TextureAtlas;
class SCMFile;
/**
* @brief Stores simple data about Textures such as transparency flags.
@ -104,6 +105,8 @@ public:
void loadHandling(const std::string& path);
SCMFile* loadSCM(const std::string& path);
/**
* Loads water level data
*/

View File

@ -17,6 +17,8 @@ class VehicleObject;
class WeaponScan;
class ScriptMachine;
#include <glm/glm.hpp>
#include <btBulletDynamicsCommon.h>
@ -78,6 +80,8 @@ public:
*/
bool defineItems(const std::string& name);
void runScript(const std::string& name);
/**
* Loads an IPL into the game.
* @param name The name of the IPL as it appears in the games' gta.dat
@ -214,6 +218,8 @@ public:
* Work related
*/
WorkContext* _work;
ScriptMachine* script;
};
#endif

View File

@ -0,0 +1,12 @@
#pragma once
#ifndef _OPCODES_3_HPP_
#define _OPCODES_3_HPP_
#include <script/ScriptTypes.hpp>
struct Opcodes3 : SCMOpcodes
{
Opcodes3();
};
#endif

View File

@ -1,12 +1,9 @@
#pragma once
#ifndef _SCMFILE_HPP_
#define _SCMFILE_HPP_
#include <cstdint>
#include <vector>
#include <string>
typedef uint16_t SCMOpcode;
typedef char SCMByte;
#include <script/ScriptTypes.hpp>
/**
* @brief Handles in-memory SCM file data including section offsets.

View File

@ -1,10 +1,16 @@
#pragma once
#ifndef _SCRIPTMACHINE_HPP_
#define _SCRIPTMACHINE_HPP_
#include <script/ScriptTypes.hpp>
#include <sstream>
#include <iomanip>
#include <string>
#include <map>
#include <vector>
#define SCM_CONDITIONAL_MASK 0xF000
#define SCM_THREAD_LOCAL_SIZE 256
class SCMFile;
struct SCMException
{
@ -16,16 +22,18 @@ struct IllegalInstruction : SCMException
{
SCMOpcode opcode;
unsigned int offset;
std::string thread;
IllegalInstruction(SCMOpcode opcode, unsigned int offset)
: opcode(opcode), offset(offset) { }
IllegalInstruction(SCMOpcode opcode, unsigned int offset, const std::string& thread)
: opcode(opcode), offset(offset), thread(thread) { }
std::string what() const {
std::stringstream ss;
ss << "Illegal Instruction " <<
std::setfill('0') << std::setw(4) << std::hex << opcode <<
" encountered at offset " <<
std::setfill('0') << std::setw(4) << std::hex << offset;
std::setfill('0') << std::setw(4) << std::hex << offset <<
" on thread " << thread;
return ss.str();
}
};
@ -34,54 +42,56 @@ struct UnknownType : SCMException
{
SCMByte type;
unsigned int offset;
std::string thread;
UnknownType(SCMByte type, unsigned int offset)
: type(type), offset(offset) {}
UnknownType(SCMByte type, unsigned int offset, const std::string& thread)
: type(type), offset(offset), thread(thread) {}
std::string what() const {
std::stringstream ss;
ss << "Unkown data type " <<
std::setfill('0') << std::hex << static_cast<unsigned int>(type) <<
" encountered at offset " <<
std::setfill('0') << std::hex << offset;
std::setfill('0') << std::hex << offset <<
" on thread " << thread;
return ss.str();
}
};
struct SCMMicrocode {
static SCMMicrocodeTable knownOps;
struct SCMThread
{
typedef unsigned int pc_t;
std::string name;
int parameters;
pc_t programCounter;
/** Number of MS until the thread should be waked (-1 = yeilded) */
int wakeCounter;
SCMByte locals[SCM_THREAD_LOCAL_SIZE];
};
typedef std::map<SCMOpcode, SCMMicrocode> SCMMicrocodeTable;
class ScriptMachine
{
SCMFile* _file;
SCMOpcodes* _ops;
SCMMicrocodeTable knownOps;
std::vector<SCMThread> _activeThreads;
enum SCMType {
EndOfArgList = 0x00,
TInt32 = 0x01,
TGlobal = 0x02,
TLocal = 0x03,
TInt8 = 0x04,
TInt16 = 0x05,
TFloat16 = 0x06,
TString = 0x09,
};
void executeThread(SCMThread& t, int msPassed);
struct SCMTypeInfo {
uint8_t size;
};
public:
ScriptMachine(SCMFile* file, SCMOpcodes* ops);
~ScriptMachine();
typedef std::map<SCMType, SCMTypeInfo> SCMTypeInfoTable;
void startThread(SCMThread::pc_t start);
SCMTypeInfoTable typeData = {
{TInt8, {1}},
{TInt16, {2}},
{TInt32, {4}},
{TGlobal, {2}},
{TLocal, {2}},
{TFloat16,{2}},
{EndOfArgList, {0}},
SCMByte* getGlobals();
/**
* @brief executes threads until they are all in waiting state.
*/
void execute(float dt);
};
#endif

View File

@ -0,0 +1,70 @@
#pragma once
#ifndef _SCRIPTTYPES_HPP_
#define _SCRIPTTYPES_HPP_
#include <cstdint>
#include <map>
#include <vector>
#include <functional>
class ScriptMachine;
class SCMThread;
typedef uint16_t SCMOpcode;
typedef char SCMByte;
enum SCMType {
EndOfArgList = 0x00,
TInt32 = 0x01,
TGlobal = 0x02,
TLocal = 0x03,
TInt8 = 0x04,
TInt16 = 0x05,
TFloat16 = 0x06,
TString = 0x09,
};
struct SCMTypeInfo {
uint8_t size;
};
typedef std::map<SCMType, SCMTypeInfo> SCMTypeInfoTable;
static SCMTypeInfoTable typeData = {
{TInt8, {1}},
{TInt16, {2}},
{TInt32, {4}},
{TGlobal, {2}},
{TLocal, {2}},
{TFloat16,{2}},
{EndOfArgList, {0}},
};
struct SCMOpcodeParameter {
SCMType type;
union {
int integer;
float real;
char string[8];
void* globalPtr;
int* globalInteger;
float* globalReal;
};
};
typedef std::vector<SCMOpcodeParameter> SCMParams;
struct SCMMicrocode {
std::string name;
int parameters;
std::function<void (ScriptMachine*, SCMThread*, std::vector<SCMOpcodeParameter>*)> func;
};
typedef std::map<SCMOpcode, SCMMicrocode> SCMMicrocodeTable;
struct SCMOpcodes
{
std::map<SCMOpcode, SCMMicrocode> codes;
};
#endif

View File

@ -7,6 +7,7 @@
#include <loaders/LoaderCOL.hpp>
#include <data/ObjectData.hpp>
#include <data/WeaponData.hpp>
#include <script/SCMFile.hpp>
#include <iostream>
#include <fstream>
@ -347,6 +348,25 @@ void GameData::loadHandling(const std::string& path)
}
}
SCMFile *GameData::loadSCM(const std::string &path)
{
std::ifstream f(datpath + "/" + path);
if(! f.is_open() ) return nullptr;
f.seekg(0, std::ios_base::end);
unsigned int size = f.tellg();
f.seekg(0);
char* buff = new char[size];
f.read(buff, size);
SCMFile* scm = new SCMFile;
scm->loadFile(buff, size);
delete buff;
return scm;
}
void GameData::loadWaterpro(const std::string& path)
{
std::ifstream ifstr(path.c_str());

View File

@ -7,6 +7,9 @@
#include <data/WeaponData.hpp>
#include <WorkContext.hpp>
#include <script/Opcodes3.hpp>
#include <script/ScriptMachine.hpp>
// 3 isn't enough to cause a factory.
#include <objects/CharacterObject.hpp>
#include <objects/InstanceObject.hpp>
@ -67,7 +70,7 @@ public:
GameWorld::GameWorld(const std::string& path)
: gameTime(0.f), gameData(path), renderer(this), randomEngine(rand()),
_work( new WorkContext( this ) )
_work( new WorkContext( this ) ), script(nullptr)
{
gameData.engine = this;
}
@ -167,6 +170,19 @@ bool GameWorld::defineItems(const std::string& name)
return false;
}
void GameWorld::runScript(const std::string &name)
{
SCMFile* f = gameData.loadSCM(name);
if( f ) {
if( script ) delete script;
script = new ScriptMachine(f, new Opcodes3);
}
else {
logError("Failed to load SCM: " + name);
}
}
bool GameWorld::placeItems(const std::string& name)
{
auto i = gameData.iplLocations.find(name);

View File

@ -0,0 +1,94 @@
#include <script/Opcodes3.hpp>
#include <script/ScriptMachine.hpp>
SCMMicrocodeTable ops_global = {
{ 0x0002,
{
"Jump",
1,
[](ScriptMachine*, SCMThread* t, SCMParams* p)
{
t->programCounter = p->at(0).integer;
}
}
},
{ 0x0004,
{
"Set Global Integer",
2,
[](ScriptMachine*, SCMThread*, SCMParams* p)
{
*p->at(0).globalInteger = p->at(1).integer;
}
}
},
{ 0x0005,
{
"Set Global Float",
2,
[](ScriptMachine*, SCMThread*, SCMParams* p)
{
*p->at(0).globalReal = p->at(1).real;
}
}
},
{ 0x0015,
{
"Divide Global by Float",
2,
[](ScriptMachine*, SCMThread*, SCMParams* p)
{
*p->at(0).globalReal /= p->at(1).real;
}
}
},
{ 0x0086,
{
"Set Global Float To Global",
2,
[](ScriptMachine*, SCMThread*, SCMParams* p)
{
*p->at(0).globalReal = *p->at(1).globalReal;
}
}
},
{ 0x03A4,
{
"Name Thread",
1,
[](ScriptMachine*, SCMThread* t, SCMParams* p)
{
t->name = p->at(0).string;
}
}
},
};
#define SCM_FUNC(c) [](ScriptMachine* m, SCMThread* t, SCMParams* p) c
#include <iostream>
SCMMicrocodeTable ops_game = {
{ 0x042C,
{ "Set Total Missions", 1,
SCM_FUNC({
std::cout << "Total Missions: " << p->at(0).integer << std::endl;
})
}
}
};
Opcodes3::Opcodes3()
{
codes.insert(ops_global.begin(), ops_global.end());
codes.insert(ops_game.begin(), ops_game.end());
}

View File

@ -0,0 +1,111 @@
#include <script/ScriptMachine.hpp>
#include <script/SCMFile.hpp>
void ScriptMachine::executeThread(SCMThread &t, int msPassed)
{
if( t.wakeCounter > 0 ) {
t.wakeCounter = std::max( t.wakeCounter - msPassed, 0 );
}
if( t.wakeCounter > 0 ) return;
while( t.wakeCounter == 0 ) {
auto opcode = _file->read<SCMOpcode>(t.programCounter);
auto it = _ops->codes.find(opcode);
if( it == _ops->codes.end() ) throw IllegalInstruction(opcode, t.programCounter, t.name);
t.programCounter += sizeof(SCMOpcode);
SCMMicrocode& code = it->second;
SCMParams parameters;
for( int p = 0; p < code.parameters; ++p ) {
auto type_r = _file->read<SCMByte>(t.programCounter);
auto type = static_cast<SCMType>(type_r);
if( type_r > 42 ) {
// for implicit strings, we need the byte we just read.
type = TString;
}
else {
t.programCounter += sizeof(SCMByte);
}
parameters.push_back(SCMOpcodeParameter { type, 0 });
switch(type) {
case TInt8:
parameters.back().integer = _file->read<std::uint8_t>(t.programCounter);
t.programCounter += sizeof(SCMByte);
break;
case TInt16:
parameters.back().integer = _file->read<std::uint16_t>(t.programCounter);
t.programCounter += sizeof(SCMByte) * 2;
break;
case TGlobal: {
auto v = _file->read<std::uint16_t>(t.programCounter);
parameters.back().globalPtr = getGlobals() + v;
t.programCounter += sizeof(SCMByte) * 2;
}
break;
case TInt32:
parameters.back().integer = _file->read<std::uint32_t>(t.programCounter);
t.programCounter += sizeof(SCMByte) * 4;
break;
case TString:
std::copy(_file->data()+t.programCounter, _file->data()+t.programCounter+8,
parameters.back().string);
t.programCounter += sizeof(SCMByte) * 8;
break;
case TFloat16:
parameters.back().real = (float)(_file->read<std::uint16_t>(t.programCounter)) / 16.f;
t.programCounter += sizeof(SCMByte) * 2;
break;
default:
throw UnknownType(type, t.programCounter, t.name);
break;
};
}
code.func(this, &t, &parameters);
}
if( t.wakeCounter == -1 ) {
t.wakeCounter = 0;
}
}
ScriptMachine::ScriptMachine(SCMFile *file, SCMOpcodes *ops)
: _file(file), _ops(ops)
{
startThread(0);
}
ScriptMachine::~ScriptMachine()
{
delete _file;
delete _ops;
}
void ScriptMachine::startThread(SCMThread::pc_t start)
{
SCMThread t;
for(int i = 0; i < SCM_THREAD_LOCAL_SIZE; ++i) {
t.locals[i] = 0;
}
t.name = "THREAD";
t.programCounter = start;
t.wakeCounter = 0;
_activeThreads.push_back(t);
}
SCMByte *ScriptMachine::getGlobals()
{
return _file->data() + _file->getGlobalSection();
}
void ScriptMachine::execute(float dt)
{
int ms = dt * 1000.f;
for(auto& thread : _activeThreads) {
executeThread( thread, ms );
}
}

View File

@ -7,9 +7,20 @@
#include <objects/ItemPickup.hpp>
#include <render/Model.hpp>
#include <items/WeaponItem.hpp>
#include <script/ScriptMachine.hpp>
IngameState::IngameState()
IngameState::IngameState(bool test)
: _player(nullptr), _playerCharacter(nullptr)
{
if( test ) {
startTest();
}
else {
getWorld()->runScript("data/main.scm");
}
}
void IngameState::startTest()
{
_playerCharacter = getWorld()->createPedestrian(1, {-1000.f, -990.f, 13.f});
_player = new PlayerController(_playerCharacter);
@ -72,17 +83,7 @@ void IngameState::spawnPlayerVehicle()
}
}
void IngameState::enter()
{
}
void IngameState::exit()
{
}
void IngameState::tick(float dt)
void IngameState::updateView()
{
float qpi = glm::half_pi<float>();
@ -153,6 +154,34 @@ void IngameState::tick(float dt)
setViewParameters( viewPos + localview * viewFraction, {localX, _lookAngles.y} );
}
void IngameState::enter()
{
}
void IngameState::exit()
{
}
void IngameState::tick(float dt)
{
if( _player ) {
updateView();
}
if( getWorld()->script ) {
try {
getWorld()->script->execute(dt);
}
catch( SCMException& ex ) {
std::cerr << ex.what() << std::endl;
getWorld()->logError( ex.what() );
throw;
}
}
}
void IngameState::handleEvent(const sf::Event &event)
{
switch(event.type) {

View File

@ -13,10 +13,13 @@ class IngameState : public State
glm::vec2 _lookAngles;
glm::vec3 _movement;
public:
IngameState();
IngameState(bool test = false);
void startTest();
void spawnPlayerVehicle();
void updateView();
virtual void enter();
virtual void exit();

View File

@ -6,7 +6,8 @@ MenuState::MenuState()
{
Menu *m = new Menu(getFont());
m->offset = glm::vec2(50.f, 100.f);
m->addEntry(Menu::lambda("Test", [] { StateManager::get().enter(new IngameState); }));
m->addEntry(Menu::lambda("Start", [] { StateManager::get().enter(new IngameState); }));
m->addEntry(Menu::lambda("Test", [] { StateManager::get().enter(new IngameState(true)); }));
m->addEntry(Menu::lambda("Options", [] { std::cout << "Options" << std::endl; }));
m->addEntry(Menu::lambda("Exit", [] { getWindow().close(); }));
this->enterMenu(m);

View File

@ -49,7 +49,7 @@ void dumpOpcodes(SCMFile* scm, unsigned int offset, unsigned int size)
// If we don't know the size of the operator's parameters we can't jump over it.
if( opit == knownOps.end() ) {
throw IllegalInstruction(op, i);
throw IllegalInstruction(op, i, "");
}
std::cout << std::hex << std::setfill('0') << std::right <<
@ -67,7 +67,7 @@ void dumpOpcodes(SCMFile* scm, unsigned int offset, unsigned int size)
auto typeit = typeData.find(static_cast<SCMType>(datatype));
if( typeit == typeData.end()) {
if( datatype < 0x06 ) {
throw UnknownType(datatype, i);
throw UnknownType(datatype, i, "");
}
else {
datatype = TString;

View File

@ -0,0 +1,29 @@
#include <boost/test/unit_test.hpp>
#include "test_globals.hpp"
#include <script/ScriptMachine.hpp>
#include <script/SCMFile.hpp>
SCMByte data[] = {
0x02,0x00,0x01,0x08,0x00,0x00,0x00,0x00,
0x02,0x00,0x01,0x18,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x02,0x00,0x01,0x28,0x00,0x00,0x00,0x00,
0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00
};
BOOST_AUTO_TEST_SUITE(ScriptMachineTests)
BOOST_AUTO_TEST_CASE(scmfile_test)
{
SCMFile f;
f.loadFile(data, sizeof(data));
BOOST_CHECK_EQUAL( f.getModelSection(), 0x10 );
BOOST_CHECK_EQUAL( f.getMissionSection(), 0x20 );
BOOST_CHECK_EQUAL( f.getCodeSection(), 0x28 );
}
BOOST_AUTO_TEST_SUITE_END()