rathena/src/plugins/console.c
FlavioJS 6e27509f86 - Console plugin working when built as Windows native.
The linux part has the same strategy but uses the new asynchronous input abstraction layer.
The abstraction layer is there to facilitate changes, was really handy when trying to get it working as windows native. :)

git-svn-id: https://svn.code.sf.net/p/rathena/svn/trunk@9641 54d463be-8e91-2dee-dedb-b68131a5f0ec
2007-01-11 08:02:45 +00:00

501 lines
12 KiB
C

// Copyright (c) Athena Dev Teams - Licensed under GNU GPL
// For more information, see LICENCE in the main folder
#include "../common/plugin.h"
#ifdef WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#else
#include <sys/types.h>
#include <unistd.h>
#include <poll.h>
#include <string.h>
#endif
#include <stdio.h> // stdin, fgets
#define INPUT_BUFSIZE 4096
#define INPUT_INVALID 0
#define INPUT_READY 1
#define INPUT_WAITING 2
#define INPUT_READING 3
#define INPUT_CLOSED 4
//////////////////////////////
#ifdef WIN32
//////////////////////////////
// In windows the worker is a thread so it can access the same variables.
#define WORKER_FUNC_DECLARE(name) DWORD WINAPI worker_ ## name(LPVOID lpParameter)
#define WORKER_FUNC_START(name) DWORD WINAPI worker_ ## name(LPVOID lpParameter) { (void)lpParameter; {
#define WORKER_FUNC_END(name) } ExitThread(0); return 0; }
#define WORKER_EXECUTE(name,errvar) \
do{ \
buf.worker = CreateThread(NULL, 0, worker_ ## name, NULL, CREATE_SUSPENDED, NULL); \
if( errvar ) \
*errvar = ( buf.worker == NULL ); \
}while(0)
/// Buffer for asynchronous input
typedef struct _buffer {
char arr[INPUT_BUFSIZE];
size_t len;
HANDLE worker;
HANDLE state_mux; // mutex for the state
char state;
} BUFFER;
//////////////////////////////
#else
//////////////////////////////
/// In linux the worker is a process so it needs to comunicate through pipes.
#define WORKER_FUNC_DECLARE(name) void worker_ ## name(void)
#define WORKER_FUNC_START(name) void worker_ ## name(void) {
#define WORKER_FUNC_END(name) _exit(0); }
#define WORKER_EXECUTE(name,errvar) \
do{ \
int pid = fork(); \
if( pid == 0 ){ \
worker_ ## name(); \
} \
if( errvar ) \
*errvar = (pid == -1); \
}while(0)
#define PIPE_READ 0
#define PIPE_WRITE 1
/// Buffer for asynchronous input
typedef struct _buffer {
char arr[INPUT_BUFSIZE];
size_t len;
int data_pipe[2]; // pipe to receive data
int state_pipe[2]; // pipe to send state
char state;
unsigned close_unused_flag : 1;
} BUFFER;
//////////////////////////////
#endif
//////////////////////////////
////// Plugin information ////////
PLUGIN_INFO = {
"Console", // Name
PLUGIN_ALL, // Target servers
"0.1", // Version
"1.03", // Minimum plugin engine version to run
"Console parser" // Short description
};
////// Plugin event list //////////
// Format: <plugin function>,<event name>
// All registered functions to a event gets executed
// (In descending order) when its called.
// Multiple functions can be called by multiple events too,
// So it's up to your creativity ^^
//
PLUGIN_EVENTS_TABLE = {
{ "console_init", EVENT_PLUGIN_INIT },
{ "console_final", EVENT_PLUGIN_FINAL },
{ "console_autostart", EVENT_ATHENA_INIT },
//{ "console_start", EVENT_CONSOLE_START },//## add these events to the plugins framework
//{ "console_stop", EVENT_CONSOLE_STOP },
{ "console_stop", EVENT_ATHENA_FINAL },
{ NULL, NULL }
};
///// Variables /////
// Imported functions
typedef int TimerFunc(int tid, unsigned int tick, int id, int data);
int (*add_timer_func_list)(TimerFunc func, char* name);
int (*add_timer_interval)(unsigned int tick, TimerFunc* func, int id, int data, int interval);
int (*delete_timer)(int tid, TimerFunc* func);
unsigned int (*gettick)(void);
int (*parse_console)(char* buf);
// Locals
int tid; // timer id
BUFFER buf; // input buffer
WORKER_FUNC_DECLARE(getinput); // worker for the input buffer
//////// Asynchronous input functions //////////
//////////////////////////////
#ifdef WIN32
//////////////////////////////
//
// --=== Asynchronous console input ===--
//
// On windows a thread is used (both threads have access to the same data).
// The worker threads starts suspended and is resumed when data is required.
// After getting the data, the worker thread updates the state variable and
// suspends itself.
//
// A mutex is used to synchronize access to the state variable between the
// threads. Access and updates to state are probably already atomic so the
// mutex shouldn't be needed, but using it is more correct so it stays.
//
// Note: The Worker thread only starts to get input data when further data is
// requested. This is a design choise and brings no real advantage or
// disadvantage I can think of.
//
/// Returns the state of the input
char input_getstate()
{
char state;
WaitForSingleObject(buf.state_mux, INFINITE);
state = buf.state;
ReleaseMutex(buf.state_mux);
return state;
}
/// Sets the state of the input
void input_setstate(char state)
{
char oldstate;
// update state
WaitForSingleObject(buf.state_mux, INFINITE);
oldstate = buf.state;
buf.state = state;
ReleaseMutex(buf.state_mux);
if( state == INPUT_READY && oldstate == INPUT_READING )
{// data has become available
SuspendThread(buf.worker);
} else if( state == INPUT_WAITING )
{// input is waiting for data
ResumeThread(buf.worker);
//} else if( state == INPUT_READING )
//{// worker is reading data
} else if( state == INPUT_CLOSED )
{// end the input
CloseHandle(buf.state_mux);
TerminateThread(buf.worker, 0);
}
}
/// Gets the next state of the input
#define input_nextstate() input_getstate()
/// Returns if data is available from asynchronous input.
/// Requests data if none is available.
int input_hasdata(void)
{
if( input_getstate() == INPUT_READY )
{// buffer is ready
if( buf.len > 0 )
return 1; // data found ;)
// request data from the worker
input_setstate(INPUT_WAITING);
}
return 0; // no data
}
/// Initialize asynchronous input
int input_init(void)
{
int err = 0;
memset(&buf, 0, sizeof(buf));
buf.state_mux = CreateMutex(NULL, FALSE, NULL);
if( buf.state_mux == NULL )
{// failed to create state mutex
return 1;
}
buf.len = 0;
input_setstate(INPUT_READY);
WORKER_EXECUTE(getinput, &err);
if( err )
{// failed to start worker
input_setstate(INPUT_CLOSED);
}
return err;
}
/// Finalize asynchronous input
int input_final(void)
{
input_setstate(INPUT_CLOSED);
return 0;
}
//////////////////////////////
#else
//////////////////////////////
//
// --=== Asynchronous console input ===--
//
// On the other systems a process is used and pipes are used to comunicate.
// The worker process receives status updates through one of the pipes either
// requesting data or ending the worker.
// The other pipe is used by the worker to send the input data and is checked
// for data by the main thread in the timer function.
//
// Note: The Worker thread only starts to get input data when further data is
// requested. This is a design choise and brings no real advantage or
// disadvantage I can think of.
//
/// Returns the state of the input
#define input_getstate() buf.state
/// Sets the state of the input
void input_setstate(char state)
{
if( state == INPUT_READY && input_getstate() == INPUT_READING )
{// send data from the worker to the main process
write(buf.data_pipe[PIPE_WRITE], &buf.len, sizeof(buf.len));
write(buf.data_pipe[PIPE_WRITE], &buf.arr, buf.len);
} else if( state == INPUT_WAITING ){
if( buf.close_unused_flag == 0 )
{// close unused pipe sides in the main process
close(buf.data_pipe[PIPE_WRITE]);
close(buf.state_pipe[PIPE_READ]);
buf.close_unused_flag = 1;
}
// send the next state
write(buf.state_pipe[PIPE_WRITE], &state, sizeof(state));
} else if( state == INPUT_READING ){
if( buf.close_unused_flag == 0 )
{// close unused pipe sides in the worker process
close(buf.data_pipe[PIPE_READ]);
close(buf.state_pipe[PIPE_WRITE]);
buf.close_unused_flag = 1;
}
} else if( state == INPUT_CLOSED )
{// send next state to the worker and close the pipes
write(buf.state_pipe[PIPE_WRITE], &state, sizeof(state));
close(buf.data_pipe[PIPE_WRITE]);
close(buf.data_pipe[PIPE_READ]);
close(buf.state_pipe[PIPE_WRITE]);
close(buf.state_pipe[PIPE_READ]);
}
buf.state = state;
}
/// Waits for the next state of the input
char input_nextstate()
{
char state = INPUT_CLOSED;
int bytes = 0;
while( bytes == 0 )
bytes = read(buf.state_pipe[PIPE_READ], &state, sizeof(state));
if( bytes == -1 )
{// error, terminate worker
input_setstate(INPUT_CLOSED);
}
return state;
}
/// Returns if data is available from asynchronous input.
/// If data is available, it's put in the local buffer.
int input_hasdata()
{
struct pollfd fds;
int hasData;
if( input_getstate() == INPUT_READY )
{// start getting data
input_setstate(INPUT_WAITING);
return 0;
}
// check if data is available
fds.fd = buf.data_pipe[PIPE_READ];
fds.events = POLLRDNORM;
hasData = ( poll(&fds,1,0) > 0 );
if( hasData )
{// read the data from the pipe
read(buf.data_pipe[PIPE_READ], &buf.len, sizeof(buf.len));
read(buf.data_pipe[PIPE_READ], buf.arr, buf.len);
input_setstate(INPUT_READY);
}
return hasData;
}
/// Initialize asynchronous input
int input_init(void)
{
int err = 0;
memset(&buf, 0, sizeof(buf));
if( pipe(buf.data_pipe) )
{// error creating data pipe
return 1;
}
if( pipe(buf.state_pipe) )
{// error creating state pipe
close(buf.data_pipe[PIPE_READ]);
close(buf.data_pipe[PIPE_WRITE]);
return 1;
}
buf.len = 0;
input_setstate(INPUT_READY);
WORKER_EXECUTE(getinput, &err);
if( err ){
//printf("input_init failed to start worker (%d)\n", err);
input_setstate(INPUT_CLOSED);
}
return err;
}
/// Finalize asynchronous input
int input_final(void)
{
close(buf.data_pipe[PIPE_READ]);
close(buf.data_pipe[PIPE_WRITE]);
close(buf.state_pipe[PIPE_READ]);
close(buf.state_pipe[PIPE_WRITE]);
return 0;
}
//////////////////////////////
#endif
//////////////////////////////
/// Returns the input data array
#define input_getdata() buf.arr
/// Returns the input data length
#define input_getlen() buf.len
/// Clear the input data
#define input_clear() ( buf.len = 0 )
/// Worker thread/process that gets input
WORKER_FUNC_START(getinput)
while( input_nextstate() != INPUT_CLOSED )
{// get input
input_setstate(INPUT_READING);
buf.arr[0] = '\0';
fgets(buf.arr, INPUT_BUFSIZE-1, stdin);
buf.len = strlen(buf.arr);
input_setstate(INPUT_READY);
}
WORKER_FUNC_END(getinput)
//////// Plugin console functions //////////
/// Timer function that checks if there's assynchronous input data and feeds parse_console()
/// The input reads one line at a time and line terminators are removed.
int console_getinputtimer(int tid, unsigned int tick, int id, int data)
{
char* cmd;
size_t len;
if( input_hasdata() ){
// get data (removes line terminators)
cmd = input_getdata();
len = input_getlen();
while( len > 0 && (cmd[len-1] == '\r' || cmd[len-1] == '\n') )
cmd[--len] = '\0';
// parse data
parse_console(cmd);
input_clear();
}
return 0;
}
/// Start the console
void console_start(void)
{
if( input_init() ){
return;
}
//##TODO add a 'startupcmd' config options
//parse_console("help");
add_timer_func_list(console_getinputtimer,"console_getinputtimer");
tid = add_timer_interval(gettick(),console_getinputtimer,0,0,250);//##TODO add a 'timerperiod' config option
}
void console_autostart(void)
{//##TODO add an 'autostart' config option
console_start();
}
/// Stop the console
void console_stop(void)
{
if( tid != -1 ){
delete_timer(tid, console_getinputtimer);
input_final();
}
return;
}
/// Test the console for compatibility
int console_test(void)
{// always compatible at the moment, maybe test if standard input is available?
return 1;
}
/// Initialize the console
void console_init(void)
{
// import symbols
IMPORT_SYMBOL(add_timer_interval, SYMBOL_ADD_TIMER_INTERVAL);
IMPORT_SYMBOL(add_timer_func_list, SYMBOL_ADD_TIMER_FUNC_LIST);
IMPORT_SYMBOL(delete_timer, SYMBOL_DELETE_TIMER);
IMPORT_SYMBOL(gettick, SYMBOL_GETTICK);
IMPORT_SYMBOL(parse_console, SYMBOL_PARSE_CONSOLE);
//printf("%d -> add_timer_func_list=0x%x\n", SYMBOL_ADD_TIMER_FUNC_LIST, (int)add_timer_func_list);
//printf("%d -> add_timer_interval=0x%x\n", SYMBOL_ADD_TIMER_INTERVAL, (int)add_timer_interval);
//printf("%d -> delete_timer=0x%x\n", SYMBOL_DELETE_TIMER, (int)delete_timer);
//printf("%d -> gettick=0x%x\n", SYMBOL_GETTICK, (int)gettick);
//printf("%d -> parse_console=0x%x\n", SYMBOL_PARSE_CONSOLE, (int)parse_console);
}
/// Finalize the console
void console_final(void)
{
}