!Snap is a development from what I learnt by reading a CAUGers past article by Tom Hughes explaining how to do easy networking from a taskwindow task. This method was new to me at this time as it was not even described in the RO3.6 PRM supplement. Briefly stated, it shows that modifications to network applications source code can be very minimalist to make them run happily under the Wimp cooperative multitasking environment. As the official Acorn network library does'nt mention the option to set, I used the NetLib library which provides otherwise at least the same level of API.
!Snap as a classic client/server application is made of two components (you already guess what they are). An iterative server application started with the command below extracted from the !Run file:
TaskWindow "RMEnsure SnapServSupport 0.00 RMRun <SnapS$Dir>.SnapS" -name SnapServer -display -wimpslot 64K -quit
The server will bind a listening socket to fixed port 15208.
Once the server handle a connection request from a client, he creates a sprite containing the whole screen of the local Acorn station, then it sends the length of the "sprite" as a word and then the sprite data; he put an end to the connection thereafter.
A WIMP client application handling all user actions for connection establishment and for displaying the transfered sprite in a WIMP window while taking care of such constraints such as the number of colors and the resolution of the client station screen.
This client application communicates with taskwindows tasks it has just started via the WIMP message mechanism (already described in a previous CAUGers article named !JustAHack). The taskwindow tasks just call module code which itself deal with all network contingencies.
*inetstat -a Active Internet connections (including servers) Proto Recv-Q Send-Q Local Address Foreign Address (state) tcp 0 0 *.snap *.* LISTENThe pattern
*.snap
might be substituted by the *.15208
on your station.
Now it's time to run the client application. Double click on the !SnapC icon to start the client application: the SnapClient icon now is visible on the iconbar.
To connect to the server, just click on the icon: the dislog box below pops up:
In our context, the server and the client will both be resident on the same station so the default IP address (which binds the loopback interface) will suit our needs. To connect to the server, just click on the Connect action button and a progress window will replace the "new connexion" window on the screen.
When the transfer is completed, the progress window disappears and is replaced by the sprite window below which shows the final result (640 x 480 x 16 greys).
static size_t mget_mode_variable(const int reason) { _kernel_swi_regs r; _kernel_oserror *p; int carry; r.r[0]= -1; r.r[1]= reason; p= _kernel_swi_c(OS_ReadModeVariable, &r, &r, &carry); if(p || carry) exit(EXIT_FAILURE); return (size_t) r.r[2]; } #define KSCRSIZE 7 #define XEIGEN 4 #define YEIGEN 5 #define XWNDLIMIT 11 #define YWNDLIMIT 12 #define KGWLCOL 128 #define KGWBROW 129 #define KGWRCOL 130 #define KGWTROW 131 #define NCOLOUR 3 void refresh(const int mode) { _kernel_swi_regs r; _kernel_oserror *p; const int reasons[] = { KGWLCOL, KGWBROW, KGWRCOL, KGWTROW, -1 }; gmode= mode; screensize= mget_mode_variable(KSCRSIZE) + 1024; xscrlimit= mget_mode_variable(XWNDLIMIT) + 1; yscrlimit= mget_mode_variable(YWNDLIMIT) + 1; xeigen= mget_mode_variable(XEIGEN); yeigen= mget_mode_variable(YEIGEN); ncolour= mget_mode_variable(NCOLOUR); r.r[0]= (int) &reasons; r.r[1]= (int) &vduvars; p= _kernel_swi(OS_ReadVduVariables, &r, &r); if(p) mexit(EXIT_FAILURE, p); reallocated= FALSE; } void spr_init(void) { int mode= _kernel_osbyte(135, 0, 0); if(mode >> 8) & 0xff); atexit(atexit1); }Whenever the screen mode changes, the module service call handler is run (triggering for service call ModeChange). Extract from the cmhg module.
service-call-handler: sc_handler 0x46Extract from the main module
void sc_handler(int service_number, _kernel_swi_regs *r, void *pw) { refresh(r->r[2]); }As soon as a connexion with a client is established, the program will serve this request up to its end before proceeding on the next connexion.
void mwrite(const int sd, const void *const pbuf, const size_t ilen) { const size_t mlenmax= 16384; size_t len= ilen; int nwritten; const char *pb= pbuf; do { nwritten= send(sd, pb, (len > mlenmax) ? mlenmax : len, 0); if(nwritten > 0); } ... int main() { ... for(;;) { connfd= accept(listenfd, (SA *) NULL, NULL); if(connfd > 0) { gconnfd= connfd; make_tw_compat(connfd); bOK= process_query(&pbuf, &nbytes); tnbytes= (size_t) htonl(nbytes); mwrite(connfd, &tnbytes, sizeof(size_t)); if(gconnfd!= -1) { if(bOK) mwrite(connfd, pbuf, nbytes); if(gconnfd!= -1) { close(connfd); gconnfd= -1; } } } } return EXIT_SUCCESS; }
And here is the core stuff of the server (creates a sprite). The reallocated BOOL
variable serves as a flag to check whether a memory reallocation is required or not (set to FALSE
as the mode changes, reset to TRUE
after having called the realloc C library function)
static const char spr_name[]= "screen"; static void *pscreen= NULL; static BOOL reallocated= FALSE; static size_t mlen= 0; static size_t ncolour; static size_t screensize; static size_t xscrlimit, yscrlimit; static size_t xeigen, yeigen; static int gmode= -1; static struct { int gwlcol, gwbrow, gwrcol, gwtrow; } vduvars; static void create_sprite(void) { _kernel_swi_regs r; _kernel_oserror *p; r.r[0]= 16 | 256; r.r[1]= (int) pscreen; r.r[2]= (int) spr_name; r.r[3]= ncolour > 256 ? 0 : 1; r.r[4]= vduvars.gwlcol<<xeigen; r.r[5]= vduvars.gwbrow<<yeigen; r.r[6]= vduvars.gwrcol<<xeigen; r.r[7]= vduvars.gwtrow<<yeigen; p= _kernel_swi(OS_SpriteOp, &r, &r); if(p) mexit(EXIT_FAILURE, p); } BOOL process_query(void **const p2pbuf, size_t *const pnbytes) { _kernel_swi_regs r; _kernel_oserror *p; void *pstr; int *pi; size_t llen; assert(pnbytes!= NULL); *pnbytes= 0; if(!reallocated) { pstr= realloc(pscreen, llen= screensize); if(!pstr) return FALSE; pi= (int *) (pscreen= pstr); mlen= llen; *pi= llen; *(pi+1)= 0; *(pi+2)= 16; r.r[0]= 9 | 256; r.r[1]= (int) pscreen; p= _kernel_swi(OS_SpriteOp, &r, &r); if(p) mexit(EXIT_FAILURE, p); reallocated= TRUE; } create_sprite(); *pnbytes= (size_t) *(((int *) pscreen)+3); *p2pbuf= pscreen; return TRUE; }
SnapCGet <key> <serveraddress>
SnapCDone <key>
is issued (this time by a C library system call) to free memory ressources.
The module c.spriteutil is in charge of all sprite rendering on the client station whatever the current mode and palette settings.