Linking Two PlayStations 16/06/99 The libcyc library contains the functions that allow link-able yaroze games to be written, albeit in a rather limited fashion. The following text explains how to use the relevant functions within yaroze applications. The main functions are:- sio_open() opens the psx serial port link_connect() creates a connection between two playstations pkt_start() begins a packet transfer session pkt_transfer() transfers packets between playstations pkt_end() ends a packet session link_disconnect() breaks the connection between playstations cyc_exit() sends the exit command to the PC/other playstation sio_close() closes the serial port int sio_open(void) Firstly, sio_open() needs to be called near the start of the program, after the graphics system has been initialized. This function opens the serial port for transferring file data and data packets. The standard port file descriptors are closed to give exclusive port access (printf()'s and system messages are not displayed). Also within this function, the library's vsync callback function and the vsync timer values are initialized. The cyc_ping() function is also called, if a PC connection is not detected, the port baud rate is increased. If the port opening is successful 1 is returned, otherwise zero. Examples if(sio_open()) { // serial port open } or if(!sio_open()) { // problem opening serial port exit(1); } struct link *link_connect(volatile char *padbuf) Next a connection needs to be made between the two playstations. The link_connect() function is used for both PC modem and link cable connections. For PC modem connections, the player is prompted by the PC to either make or await a call to/from the remote PC. The playstation connected to the calling PC becomes the master playstation when the connection has been made, the other becomes the slave. For a link cable connection, it's possible for the player to cancel a connection attempt. If a pointer to the controller buffer is passed to the function, and the triangle button is pressed before a connection is made, pkt_connect() will immediately return to the application. Once a connection has been established via a link cable connection, the playstation that initiates the connection becomes the master, the other the slave. link_connect() always returns the address of the main link structure, whether a connection is made or not. The description of link_connect() in libcyc.txt explains the values returned within the structure, these are the fields that contain relevant values:- link_p->connected - 1 if connected link_p->loc_status - success/failure value link_p->lk_type - if connected, the link type link_p->m_state - if connected, master/slave status Examples struct link_info *link_p; // pass NULL, no user connection interruption link_p = link_connect(NULL); if(link_p->connected) { // connection made switch(link-p->lk_type) // check how we are linked { case LK_PSX: // link cable connection case LK_PC: // PC ports connection ( 2 psx's connected to 1 PC ) case LK_MODEM: // PC modem connection } } or link_p = link_connect(bb0); // allow user interruption switch(link_p->loc_status) { case I_CONNECTED: // connection made case I_USER_INT: // user interrupted connection attempt case I_CNT_FAIL: // failed to connect } int pkt_start(u_int size, int mode) With a connection established, pkt_start() is called to start a packet transfer session. The size in bytes of the data that will be sent within the session is specified by 'size'. Up to 6 bytes of data can be transferred every game frame (is that all?, for now yes), and the data size must match that of the other playstation. Set the mode as P_TFR_REAL when connected and you want to transfer packets to another playstation. It's possible to run test transfers to the PC when Cyclone is running. Packets can be sent from the playstation to the PC, which are then sent straight back to the playstation. To do this, call pkt_start() with the mode as P_TFR_TEST. Test transfers can't be run if a PC connection to another playstation has already been established. pkt_start() returns 1(S_READY) if both playstations are ready to start transferring packets, zero(S_NO_CONNECT) if the playstations are not connected, -1(S_START_FAIL) if the session cannot be started due to a mismatch in the data size or there's a problem with the PC connection. -2(S_BAD_SIZE) is returned if data size is out of range, and -3(S_MEM_FAIL) if the memory required for packet space could not be allocated. Examples if(pkt_start(2,P_TFR_TEST) == S_READY) { // playstations ready to transfer } or rtn_val = pkt_start(6,P_TFR_REAL); switch(rtn_val) { case S_READY: // playstations ready to begin transfer case S_NO_CONNECT: // not connected, can't start P_TFR_REAL transfer case S_START_FAIL: // session could not be started case S_BAD_SIZE: // 'size' out of range (not 1 - 6) case S_MEM_FAIL: // malloc failure } int pkt_transfer(u_char *data_p) Once a packet transfer session has started, pkt_transfer() is called every frame to send and receive packets between playstations, a pointer to the data to be sent is passed to the function, the size of which was specified in the previous call to pkt_start(). pkt_transfer() creates a data packet from the data passed to it, and gives each packet the number of the frame it belongs to, the first packet belonging to frame 0, the next frame 1 and so on up to 15, then the frame number loops back to zero. Once the packet is created it's sent to the remote playstation, and if a packet from the remote has arrived it's read in and stored. If at any time the difference between the number of local packets stored and remote packets received becomes greater than link_p->max_diff, pkt_transfer() loops internally until the difference is within bounds or a time-out occurs. Monitoring the difference this way prevents one playstation getting too far ahead of the other. When data from both the local and remote playstations is stored, pkt_transfer() returns 1(T_DATA_AVAIL) and the pointers within the link structure, link_p->loc_data and link_p->rem_data point to the local and remote packet data for the next frame (frame 0 for the very first frame, frame 1 after that, etc). If pkt_transfer() returns zero(T_NO_DATA) then a remote packet did not arrive this frame, no processing should occur within the frame that could alter related data on one playstation and not on the other, only do the required in-frame processing when packet data for the next frame is available from both playstations. If pkt_transfer() returns -1(T_TFR_ENDED) then the remote playstation has called pkt_end() and is ending the packet transfer session. The local playstation should stop sending packets and makes it's own call to pkt_end(). If -2(T_TIMEOUT) is returned, a problem in the transfer has occurred and remote packets are no longer being received, pkt_end needs to be called to end the transfer session correctly. The connection is also considered broken if a time-out occurs. Example #define SIZE 4 // size of data specified in pkt_start() u_long loc_padd,rem_padd; // data used for each game frame u_long *loc_p = &loc_padd; u_long *rem_p = &rem_padd; u_long padd; // get pad data for this frame padd = Read_Pad(); // send pad data to remote, read remote packet if one has arrived rtn_val = pkt_transfer((u_char *)&padd); switch(rtn_val) { case T_DATA_AVAIL: // data available for the next frame loc_p = link_p->loc_data; rem_p = rem_p->rem_data; // get data for next game frame for(i = 0; i < SIZE; i++) { loc_p[i] = link_p->loc_data[i]; rem_p[i] = link_p->rem_data[i]; } Do_Frame_Processing(); break; case T_NO_DATA: // no (remote) data available for next game frame break; case T_TFR_ENDED: // remote playstation is ending transfer session pkt_end(); break; case T_TIMEOUT: // remote no longer sending packets, end transfer pkt_end(); break; } void pkt_end(void) Call this function when you wish to bring the transfer session to an end, or if the return value from pkt_transfer is less than zero. Example rtn_val = pkt_transfer((u_char *)&padd); if(rtn_val == T_DATA_AVAIL) { Do_Frame_Processing(); if(game_over) { pkt_end(); return; } } else { if(rtn_val < 0) { // transfer session at an end pkt_end(); } } void pkt_disconnect(void) This breaks the connection between playstations. int cyc_exit(void) This function is used to inform the other playstation and/or PC that this playstation is about to exit. The playstation receiving this command has the value link_p->cmd_info.exit set to 1. The exit command can only be sent whilst connected (link_p->connected = 1), or if the playstation is connected to the PC/Cyclone. cyc_exit() returns 1 if the exit command was sent and received successfully, zero if the command could not be sent, and -1 if there was no response received. The linkdemo program gives examples of the above functions in a working program. The time taken to pass data between playstations is quite considerable. In the link demo, test transfers between playstation and PC take around 13 hsyncs, and playstation-to-playstation transfers, using a higher baud rate, around 8 hsyncs. In a real yaroze application, with much more processing going on (and possibly higher interrupt/processing priorities), the time taken to transfer data is much longer, maybe 30-40 hsyncs per frame, and the higher psx<->psx baud rate saves only 3/4 hsyncs. Therefore, a 10-15% loss of processing time per frame should be expected when creating link-able yaroze games.