From 7646fa6aa3f04900a85a5c7b65d77d1ef9569c32 Mon Sep 17 00:00:00 2001 From: Mathias Rav Date: Mon, 29 Oct 2012 22:18:50 +0100 Subject: [PATCH] Add dfstream plugin. Broadcasts the Dwarf Fortress display on TCP port 8008. For use with https://github.com/Mortal/dfstream --- plugins/CMakeLists.txt | 1 + plugins/dfstream.cpp | 413 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 414 insertions(+) create mode 100644 plugins/dfstream.cpp diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 91d578215..f19947325 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -127,6 +127,7 @@ if (BUILD_SUPPORTED) # not yet. busy with other crud again... #DFHACK_PLUGIN(versionosd versionosd.cpp) DFHACK_PLUGIN(misery misery.cpp) + DFHACK_PLUGIN(dfstream dfstream.cpp LINK_LIBRARIES clsocket dfhack-tinythread) endif() diff --git a/plugins/dfstream.cpp b/plugins/dfstream.cpp new file mode 100644 index 000000000..0815146b0 --- /dev/null +++ b/plugins/dfstream.cpp @@ -0,0 +1,413 @@ +// TODO: which of these includes are actually needed? +#include "Core.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "df/graphic.h" +#include "df/enabler.h" +#include "df/viewscreen_unitlistst.h" +#include "df/interface_key.h" +#include "df/unit.h" +#include "df/unit_soul.h" +#include "df/unit_skill.h" +#include "df/creature_graphics_role.h" +#include "df/creature_raw.h" +#include "df/caste_raw.h" + +using namespace DFHack; +using namespace df::enums; + +using df::global::gps; +using df::global::enabler; + +//--- SNIP class definition from g_src ---------------------------------------- +enum zoom_commands { zoom_in, zoom_out, zoom_reset, zoom_fullscreen, zoom_resetgrid }; + +class renderer { + void cleanup_arrays(); +protected: + friend class renderer_decorator; + unsigned char *screen; + long *screentexpos; + char *screentexpos_addcolor; + unsigned char *screentexpos_grayscale; + unsigned char *screentexpos_cf; + unsigned char *screentexpos_cbr; + // For partial printing: + unsigned char *screen_old; + long *screentexpos_old; + char *screentexpos_addcolor_old; + unsigned char *screentexpos_grayscale_old; + unsigned char *screentexpos_cf_old; + unsigned char *screentexpos_cbr_old; + + //void gps_allocate(int x, int y); + //Either screen_to_texid(int x, int y); +public: + //void display(); + virtual void update_tile(int x, int y) = 0; + virtual void update_all() = 0; + virtual void render() = 0; + virtual void set_fullscreen(); // Should read from enabler.is_fullscreen() + virtual void zoom(zoom_commands cmd); + virtual void resize(int w, int h) = 0; + virtual void grid_resize(int w, int h) = 0; + //void swap_arrays(); + renderer() { + screen = NULL; + screentexpos = NULL; + screentexpos_addcolor = NULL; + screentexpos_grayscale = NULL; + screentexpos_cf = NULL; + screentexpos_cbr = NULL; + screen_old = NULL; + screentexpos_old = NULL; + screentexpos_addcolor_old = NULL; + screentexpos_grayscale_old = NULL; + screentexpos_cf_old = NULL; + screentexpos_cbr_old = NULL; + } + virtual ~renderer(); + virtual bool get_mouse_coords(int &x, int &y) = 0; + virtual bool uses_opengl(); +}; +//---- END class definition from g_src ---------------------------------------- + +// The error messages are taken from the clsocket source code +const char * translate_socket_error(CSimpleSocket::CSocketError err) { + switch (err) { + case CSimpleSocket::SocketError: + return "Generic socket error translates to error below."; + case CSimpleSocket::SocketSuccess: + return "No socket error."; + case CSimpleSocket::SocketInvalidSocket: + return "Invalid socket handle."; + case CSimpleSocket::SocketInvalidAddress: + return "Invalid destination address specified."; + case CSimpleSocket::SocketInvalidPort: + return "Invalid destination port specified."; + case CSimpleSocket::SocketConnectionRefused: + return "No server is listening at remote address."; + case CSimpleSocket::SocketTimedout: + return "Timed out while attempting operation."; + case CSimpleSocket::SocketEwouldblock: + return "Operation would block if socket were blocking."; + case CSimpleSocket::SocketNotconnected: + return "Currently not connected."; + case CSimpleSocket::SocketEinprogress: + return "Socket is non-blocking and the connection cannot be completed immediately"; + case CSimpleSocket::SocketInterrupted: + return "Call was interrupted by a signal that was caught before a valid connection arrived."; + case CSimpleSocket::SocketConnectionAborted: + return "The connection has been aborted."; + case CSimpleSocket::SocketProtocolError: + return "Invalid protocol for operation."; + case CSimpleSocket::SocketFirewallError: + return "Firewall rules forbid connection."; + case CSimpleSocket::SocketInvalidSocketBuffer: + return "The receive buffer point outside the process's address space."; + case CSimpleSocket::SocketConnectionReset: + return "Connection was forcibly closed by the remote host."; + case CSimpleSocket::SocketAddressInUse: + return "Address already in use."; + case CSimpleSocket::SocketInvalidPointer: + return "Pointer type supplied as argument is invalid."; + case CSimpleSocket::SocketEunknown: + return "Unknown error please report to mark@carrierlabs.com"; + default: + return "No such CSimpleSocket error"; + } +} + +// Owns the thread that accepts TCP connections and forwards messages to clients; +// has a mutex +class client_pool { + typedef tthread::mutex mutex; + + mutex clients_lock; + std::vector clients; + + // TODO - delete this at some point + tthread::thread * accepter; + + static void accept_clients(void * client_pool_pointer) { + client_pool * p = reinterpret_cast(client_pool_pointer); + CPassiveSocket socket; + socket.Initialize(); + if (socket.Listen((const uint8 *)"0.0.0.0", 8008)) { + std::cout << "Listening on a socket" << std::endl; + } else { + std::cout << "Not listening: " << socket.GetSocketError() << std::endl; + std::cout << translate_socket_error(socket.GetSocketError()) << std::endl; + } + while (true) { + CActiveSocket * client = socket.Accept(); + if (client != 0) { + lock l(*p); + p->clients.push_back(client); + } + } + } + +public: + class lock { + tthread::lock_guard l; + public: + lock(client_pool & p) + : l(p.clients_lock) + { + } + }; + friend class client_pool::lock; + + client_pool() { + accepter = new tthread::thread(accept_clients, this); + } + + // MUST have lock + bool has_clients() { + return !clients.empty(); + } + + // MUST have lock + void add_client(CActiveSocket * sock) { + clients.push_back(sock); + } + + // MUST have lock + void broadcast(const std::string & message) { + unsigned int sz = htonl(message.size()); + for (size_t i = 0; i < clients.size(); ++i) { + clients[i]->Send(reinterpret_cast(&sz), sizeof(sz)); + clients[i]->Send((const uint8 *) message.c_str(), message.size()); + } + } +}; + +// A decorator (in the design pattern sense) of the DF renderer class. +// Sends the screen contents to a client_pool. +class renderer_decorator : public renderer { + // the renderer we're decorating + renderer * inner; + + // how many frames have passed since we last sent a frame + int framesNotPrinted; + + // set to false in the destructor + bool * alive; + + // clients to which we send the frame + client_pool clients; + + // The following three methods facilitate copying of state to the inner object + void set_inner_to_null() { + inner->screen = NULL; + inner->screentexpos = NULL; + inner->screentexpos_addcolor = NULL; + inner->screentexpos_grayscale = NULL; + inner->screentexpos_cf = NULL; + inner->screentexpos_cbr = NULL; + inner->screen_old = NULL; + inner->screentexpos_old = NULL; + inner->screentexpos_addcolor_old = NULL; + inner->screentexpos_grayscale_old = NULL; + inner->screentexpos_cf_old = NULL; + inner->screentexpos_cbr_old = NULL; + } + + void copy_from_inner() { + screen = inner->screen; + screentexpos = inner->screentexpos; + screentexpos_addcolor = inner->screentexpos_addcolor; + screentexpos_grayscale = inner->screentexpos_grayscale; + screentexpos_cf = inner->screentexpos_cf; + screentexpos_cbr = inner->screentexpos_cbr; + screen_old = inner->screen_old; + screentexpos_old = inner->screentexpos_old; + screentexpos_addcolor_old = inner->screentexpos_addcolor_old; + screentexpos_grayscale_old = inner->screentexpos_grayscale_old; + screentexpos_cf_old = inner->screentexpos_cf_old; + screentexpos_cbr_old = inner->screentexpos_cbr_old; + } + + void copy_to_inner() { + inner->screen = screen; + inner->screentexpos = screentexpos; + inner->screentexpos_addcolor = screentexpos_addcolor; + inner->screentexpos_grayscale = screentexpos_grayscale; + inner->screentexpos_cf = screentexpos_cf; + inner->screentexpos_cbr = screentexpos_cbr; + inner->screen_old = screen_old; + inner->screentexpos_old = screentexpos_old; + inner->screentexpos_addcolor_old = screentexpos_addcolor_old; + inner->screentexpos_grayscale_old = screentexpos_grayscale_old; + inner->screentexpos_cf_old = screentexpos_cf_old; + inner->screentexpos_cbr_old = screentexpos_cbr_old; + } + +public: + renderer_decorator(renderer * inner, bool * alive) + : inner(inner) + , framesNotPrinted(0) + , alive(alive) + { + copy_from_inner(); + } + virtual void update_tile(int x, int y) { + copy_to_inner(); + inner->update_tile(x, y); + } + virtual void update_all() { + copy_to_inner(); + inner->update_all(); + } + virtual void render() { + copy_to_inner(); + inner->render(); + + ++framesNotPrinted; + int gfps = enabler->calculated_gfps; + if (gfps == 0) gfps = 1; + // send a frame roughly every 128 mibiseconds (1 second = 1024 mibiseconds) + if ((framesNotPrinted * 1024) / gfps <= 128) return; + + client_pool::lock lock(clients); + if (!clients.has_clients()) return; + framesNotPrinted = 0; + std::stringstream frame; + frame << gps->dimx << ' ' << gps->dimy << " 0 0 " << gps->dimx << ' ' << gps->dimy << '\n'; + unsigned char * sc_ = gps->screen; + for (int y = 0; y < gps->dimy; ++y) { + unsigned char * sc = sc_; + for (int x = 0; x < gps->dimx; ++x) { + unsigned char ch = sc[0]; + unsigned char bold = (sc[3] != 0) * 8; + unsigned char translate[] = + { 0, 4, 2, 6, 1, 5, 3, 7, 8, 12, 10, 14, 9, 13, 11, 15 }; + unsigned char fg = translate[(sc[1] + bold) % 16]; + unsigned char bg = translate[sc[2] % 16]*16; + frame.put(ch); + frame.put(fg+bg); + sc += 4*gps->dimy; + } + sc_ += 4; + } + clients.broadcast(frame.str()); + } + virtual void set_fullscreen() { inner->set_fullscreen(); } + virtual void zoom(zoom_commands cmd) { + copy_to_inner(); + inner->zoom(cmd); + } + virtual void resize(int w, int h) { + copy_to_inner(); + inner->resize(w, h); + copy_from_inner(); + } + virtual void grid_resize(int w, int h) { + copy_to_inner(); + inner->grid_resize(w, h); + copy_from_inner(); + } + virtual ~renderer_decorator() { + *alive = false; + if (inner) set_inner_to_null(); + delete inner; + inner = 0; + } + virtual bool get_mouse_coords(int &x, int &y) { return inner->get_mouse_coords(x, y); } + virtual bool uses_opengl() { return inner->uses_opengl(); } + + static renderer_decorator * hook(renderer *& ptr, bool * alive) { + renderer_decorator * r = new renderer_decorator(ptr, alive); + ptr = r; + return r; + } + + static void unhook(renderer *& ptr, renderer_decorator * dec, color_ostream & out) { + dec->copy_to_inner(); + ptr = dec->inner; + dec->inner = 0; + delete dec; + } +}; + +DFHACK_PLUGIN("dfstream"); + +inline renderer *& active_renderer() { + return reinterpret_cast(enabler->renderer); +} + +// This class is a smart pointer around a renderer_decorator. +// It should only be assigned r_d pointers that use the alive-pointer of this +// instance. +// If the r_d has been deleted by an external force, this smart pointer doesn't +// redelete it. +class auto_renderer_decorator { + renderer_decorator * p; +public: + // pass this member to the ctor of renderer_decorator + bool alive; + + auto_renderer_decorator() + : p(0) + { + } + + ~auto_renderer_decorator() { + reset(); + } + + void reset() { + if (*this) { + delete p; + p = 0; + } + } + + operator bool() { + return (p != 0) && alive; + } + + auto_renderer_decorator & operator=(renderer_decorator *p) { + reset(); + this->p = p; + } + + renderer_decorator * get() { + return p; + } + + renderer_decorator * operator->() { + return get(); + } +}; + +auto_renderer_decorator decorator; + +DFhackCExport command_result plugin_init ( color_ostream &out, vector &commands) +{ + if (!decorator) { + decorator = renderer_decorator::hook(active_renderer(), &decorator.alive); + } + return CR_OK; +} + +DFhackCExport command_result plugin_shutdown ( color_ostream &out ) +{ + decorator.reset(); + return CR_OK; +} +// vim:set sw=4 sts=4 et: