Skip to content

Commit

Permalink
Proof of concept nethack embedding interface. Very slow; will not opt…
Browse files Browse the repository at this point in the history
…imize unless demanded.
  • Loading branch information
sorear committed Aug 7, 2011
1 parent 94af0fe commit 0bf55c4
Show file tree
Hide file tree
Showing 6 changed files with 213 additions and 1 deletion.
6 changes: 5 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ set(CMAKE_CXX_FLAGS_DEBUG "-g -O0")
set(CMAKE_CXX_FLAGS_RELEASE "-O3 -s")

find_library(INETUTILS_LIBRARIES NAMES util)
find_library(DYNAMIC_LINKER NAMES dl)
find_library(THREADS NAMES pthread)

include_directories(
${SAIPH_SOURCE_DIR}/include
Expand All @@ -20,6 +22,7 @@ set(HEADER_FILES
include/Connection.h
include/Coordinate.h
include/Debug.h
include/Embed.h
include/EventBus.h
include/Globals.h
include/Inventory.h
Expand Down Expand Up @@ -182,6 +185,7 @@ set(SOURCE_FILES
src/Connection.cpp
src/Coordinate.cpp
src/Debug.cpp
src/Embed.cpp
src/EventBus.cpp
src/Inventory.cpp
src/Item.cpp
Expand Down Expand Up @@ -340,4 +344,4 @@ set(SOURCE_FILES
)

add_executable(saiph ${HEADER_FILES} ${SOURCE_FILES})
target_link_libraries(saiph ${INETUTILS_LIBRARIES})
target_link_libraries(saiph ${INETUTILS_LIBRARIES} ${DYNAMIC_LINKER} ${THREADS})
1 change: 1 addition & 0 deletions include/Connection.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#define CONNECTION_LOCAL 1
#define CONNECTION_TELNET 2
#define CONNECTION_REPLAY 3
#define CONNECTION_EMBED 4

#include <fstream>
#include <string>
Expand Down
38 changes: 38 additions & 0 deletions include/Embed.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#ifndef EMBED_H
#define EMBED_H

#include <string>
#include <vector>
#include <deque>

#include <pthread.h>

#include "Connection.h"

class Embed : public Connection {
public:
Embed();
virtual ~Embed();

virtual int transmit(const std::string& data);
virtual void start();
virtual void stop();

protected:
virtual int doRetrieve(char* buffer, int count);

private:
static pthread_t threadid;
static pthread_mutex_t mutex;
static pthread_cond_t ready;

static std::deque<char> inputq;
static std::vector<char> outputq;
static int iwaiting, exited, used;

static int hook_input();
static void hook_output(const char*, int);
static void hook_exit(int);
static void* startNetHack(void*);
};
#endif
5 changes: 5 additions & 0 deletions src/Connection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#include "Local.h"
#include "Telnet.h"
#include "Replay.h"
#include "Embed.h"

using namespace std;

Expand All @@ -27,6 +28,10 @@ Connection* Connection::create(int interface) {
return new Replay();
break;

case CONNECTION_EMBED:
return new Embed();
break;

default:
return NULL;
}
Expand Down
160 changes: 160 additions & 0 deletions src/Embed.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
#include "Embed.h"

#include "Debug.h"
#include "Globals.h"
#include "World.h"

#include <algorithm>

#include <dlfcn.h>
#include <pthread.h>

#include <stdlib.h>
#include <errno.h>
#include <string.h>

using namespace std;

typedef void (*nethack_set_hooks)(int(*)(), void(*)(const char*,int), void(*)(int));
typedef int (*nethack_main)(int, const char**);

/* constructors/destructor */
Embed::Embed() {
if (used) {
Debug::error() << "Embed cannot be used more than once per process." << endl;
World::destroy();
exit(1);
}
used = 1;

/* load nethack */
void *handle = dlopen("./libnethack.so", RTLD_NOW);
if (!handle) {
Debug::error() << "Loading nethack failed: " << dlerror() << endl;
World::destroy();
exit(1);
}

nethack_set_hooks nsh = (nethack_set_hooks) dlsym(handle, "nethack_set_hooks_1");
nethack_main nm = (nethack_main) dlsym(handle, "nethack_main_1");
if (!nsh || !nm) {
Debug::error() << "Loading nethack symbols failed: " << dlerror() << endl;
World::destroy();
exit(1);
}

std::vector<char> path(256);
while (getcwd(&path[1], path.size() - 10) == NULL) { /* @ + /nethackrc\0 */
if (errno == ERANGE) {
path.resize(path.size() * 2);
} else {
Debug::error() << "getcwd failed: " << strerror(errno) << endl;
World::destroy();
exit(1);
}
}
path[0] = '@';
strcat(&path[0], "/nethackrc");
setenv("NETHACKOPTIONS", &path[0], 1);

(*nsh)(hook_input, hook_output, hook_exit);

pthread_mutex_init(&mutex, 0);
pthread_cond_init(&ready, 0);

if (pthread_create(&threadid, 0, startNetHack, (void*) nm) != 0) {
Debug::error() << "Thread creation failed" << endl;
World::destroy();
exit(1);
}

}

Embed::~Embed() {
transmit("Sy"); // save & quit
char buf[1];
retrieve(buf, 1);
pthread_cancel(threadid);
pthread_join(threadid, 0);
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&ready);
}

/* methods */
void * Embed::startNetHack(void *arg) {
const char* argv[] = {
"nethack",
NULL
};
pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, 0);
(* (nethack_main) arg)(1, argv);
return 0; // not reached?
}

int Embed::hook_input() {
pthread_mutex_lock(&mutex);
while (inputq.size() == 0) {
iwaiting = 1;
pthread_cond_signal(&ready);
pthread_cond_wait(&ready, &mutex);
iwaiting = 0;
}
int v = inputq.front();
if (v >= 0) inputq.pop_front();
pthread_mutex_unlock(&mutex);
return v;
}

void Embed::hook_output(const char *s, int l) {
pthread_mutex_lock(&mutex);
outputq.insert(outputq.end(), s, s+l);
pthread_mutex_unlock(&mutex);
}

void Embed::hook_exit(int) {
pthread_mutex_lock(&mutex);
exited = 1;
pthread_cond_signal(&ready);
pthread_mutex_unlock(&mutex);
pthread_exit(0);
}

int Embed::doRetrieve(char* buffer, int count) {
pthread_mutex_lock(&mutex);
while (!iwaiting && !exited)
pthread_cond_wait(&ready, &mutex);

/* retrieve data */
ssize_t amount = std::min((ssize_t)count - 1, (ssize_t)outputq.size());
std::copy(outputq.begin(), outputq.begin() + amount, buffer);
outputq.empty();

buffer[amount] = '\0';
pthread_mutex_unlock(&mutex);
return (int) amount;
}

int Embed::transmit(const string& data) {
/* send data */
pthread_mutex_lock(&mutex);
inputq.insert(inputq.end(), data.begin(), data.end());
pthread_cond_signal(&ready);
pthread_mutex_unlock(&mutex);

return data.size();
}

void Embed::start() {
/* no need for some special code here */
}

void Embed::stop() {
}

/* global variables */
std::deque<char> Embed::inputq;
std::vector<char> Embed::outputq;
int Embed::iwaiting, Embed::exited, Embed::used;
pthread_t Embed::threadid;
pthread_cond_t Embed::ready;
pthread_mutex_t Embed::mutex;
4 changes: 4 additions & 0 deletions src/World.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1596,6 +1596,9 @@ int main(int argc, const char* argv[]) {
case 't':
connection_type = CONNECTION_TELNET;
break;
case 'E':
connection_type = CONNECTION_EMBED;
break;
case '0':
initial_speed = SPEED_PAUSE;
break;
Expand Down Expand Up @@ -1642,6 +1645,7 @@ int main(int argc, const char* argv[]) {
cout << "\t-l Use local nethack executable" << endl;
cout << "\t-t Use telnet nethack server" << endl;
cout << "\t-R Replay saved nethack game" << endl;
cout << "\t-E Use in-process nethack interface" << endl;
cout << "\t-T Save raw game data during play" << endl;
cout << endl;
cout << "\t-0 Start paused" << endl;
Expand Down

0 comments on commit 0bf55c4

Please sign in to comment.