/*---------------------------------------------------------------------------* Project: Revolution MPDS demo File: mpdsmodel.c Copyright 2007 Nintendo. All rights reserved. These coded instructions, statements, and computer programs contain proprietary information of Nintendo of America Inc. and/or Nintendo Company Ltd., and are protected by Federal copyright law. They may not be disclosed to third parties or copied or duplicated in any form, in whole or in part, without the prior written consent of Nintendo. $Log: mpdsmodel.c,v $ Revision 1.19 2007/11/29 03:20:27 seiki_masashi Added the const modifier. Revision 1.18 2007/11/21 06:53:14 yosizaki Reapplied the rolled-back content. Revision 1.16 2007/10/27 11:20:46 seiki_masashi Small fix Revision 1.15 2007/10/26 12:15:00 yosizaki Added ball display. Revision 1.14 2007/10/26 09:31:17 seiki_masashi Cleaned up the code Revision 1.13 2007/10/26 07:29:56 seiki_masashi Cleaned up the code Revision 1.12 2007/10/26 07:05:28 seiki_masashi Revised the link-level display Revision 1.10 2007/10/25 03:20:53 seiki_masashi Support for transformations that correspond to the ROM font encoding used by child device nicknames when they are displayed. Revision 1.8 2007/10/24 13:50:49 seiki_masashi Added a feature to display nicknames in the Lobby. Revision 1.5 2007/10/24 01:38:51 seiki_masashi Revised the method by which connection status is displayed. Revision 1.3 2007/10/23 13:43:34 seiki_masashi Revised so that pad input is sent. Revision 1.2 2007/10/23 13:08:49 seiki_masashi Changed to be multithreaded. Revision 1.1 2007/10/23 00:05:57 seiki_masashi Added the mpdsmodel demo. $NoKeywords: $ *---------------------------------------------------------------------------*/ // // This sample performs communications with the demos/wm/dataShare-Model demo in the NITRO-SDK. // // This is a demo that shows the sequence of accepting a new connection from a child at the lobby screen, and then starting a battle once the connection is closed. // // #include #include #include #include #include #include #include "rexdemo/demokpad.h" #include "rexdemo/graphic.h" #include "ball.h" #define USERHEAP_SIZE ( 65536 ) #define MY_NICKNAME L"Wii" // the nickname to display in the parent device selection screen on the child device #define MY_NICKNAME_LENGTH 3 // the number of UTF16BE characters in MY_NICKNAME (maximum of 10) #define MY_MAX_NODES 8 // maximum number of child devices that can be connected (up to a maximum of 8 devices are guaranteed in Wii Sequential communications) #define MY_DS_NODES 16 // the number of nodes, including parent devices, that perform DataSharing (matches the dataShare-Model demo in the NITRO-SDK) // for a normal application (MY_MAX_NODES+1) #define MY_DS_LENGTH 12 // the shared data size per device for DataSharing #define MY_DS_PORT 13 // the port number to use for DataSharing (matches the dataShare-Model demo in the NITRO-SDK) // When DataSharing is performed, the required transmission size is the shared size per device times the number of devices, plus 4 bytes. #define MY_PARENT_MAX_SIZE ( (MY_DS_LENGTH * MY_DS_NODES) + MPDS_HEADER_SIZE ) #define STACK_SIZE 8192 // thread stack size; you must also consider the amount used by interrupt processing #define THREAD_PRIORITY 14 // thread priority; specify a higher priority than the main thread (16) /*===========================================================================*/ // application-specific UserGameInfo // note that processing must be done in Little Endian to match the DS #pragma pack(push,1) typedef struct MyGameInfo { u8 nickName[10 * 2]; u8 nickNameLength; u8 entryCount; u16 periodicalCount; } MyGameInfo; #pragma pack(pop) // shared data structure // note that processing must be done in Little Endian to match the DS #pragma pack(push,1) typedef struct ShareData { u8 data:3; // For graphing u8 level:2; // Radio reception strength u8 command:3; // Instruction (used for switching the game states at once, etc.) u16 key; // Key data u32 count:24; // Frame count } ShareData; #pragma pack(pop) enum { SHARECMD_NONE = 0, // Nothing specific (normal) SHARECMD_EXITLOBBY, // Signal for ending the lobby screen SHARECMD_NUM }; enum { STATE_IDLE = 0, STATE_GOING_TO_LOBBY, STATE_LOBBY, STATE_GOING_TO_COMMUNICATION, STATE_COMMUNICATION, STATE_GOING_TO_IDLE }; enum { RESULT_OK = 0, RESULT_ERROR = -1, RESULT_QUIT = -2 }; /*===========================================================================*/ static const GXColor white = { 0xFF, 0xFF, 0xFF, }; static const GXColor yellow = { 0xFF, 0xFF, 0x00, }; static const GXColor gray = { 0x80, 0x80, 0x80, }; static const GXColor black = { 0x00, 0x00, 0x00, }; static const GXColor red = { 0xFF, 0x00, 0x18 }; static const GXColor green = { 0x00, 0xFF, 0x00 }; static MEMHeapHandle sUserHeap; static ShareData sRecvData[MY_MAX_NODES+1]; static BOOL sRecvFlag[MY_MAX_NODES+1]; static BOOL sConnectedFlag[MY_MAX_NODES+1]; static u8 sConnectedMacAddress[MY_MAX_NODES+1][MP_SIZE_MACADDRESS]; static u16 sConnectedNickName[MY_MAX_NODES+1][10 + 1]; static u32 sFrameCount = 0; static s32 sState = STATE_IDLE; static OSThreadQueue sStateChangeThreadQueue; static BOOL sQuitFlag = FALSE; static OSMutex sGlobalMutex; static OSThread sStateManagementThread; static OSThread sDataSharingThread; static OSThread sUpdateGameInfoThread; static u8 sStateManagementThreadStack[STACK_SIZE] ATTRIBUTE_ALIGN(32); static u8 sDataSharingThreadStack[STACK_SIZE] ATTRIBUTE_ALIGN(32); static u8 sUpdateGameInfoThreadStack[STACK_SIZE] ATTRIBUTE_ALIGN(32); static MPDSContext sDSContext; static MyGameInfo sMyGameInfo; static ENCContext sEncContext; /*===========================================================================*/ static void* StateManagementThreadFunc( void* arg ); static void* DataSharingThreadFunc( void* arg ); static void* UpdateGameInfoThreadFunc( void* arg ); static void Initialize( void ); static void MainLoop( void ); static void Draw( void ); static void SetState( s32 state ); static inline s32 GetState( void ); static s32 WaitForStateChange( void ); static void QuitMP( void ); static BOOL IsQuitting( void ); static s32 TransToLobby( void ); static s32 TransToCommunication( void ); static s32 TransToIdle( void ); static s32 DoLobbyMode( void ); static s32 DoCommunicationMode( void ); static void DataSharingCallback( s32 type, MPPortCallbackInfo* info ); static void* mpAlloc( u32 size ); static void mpFree( void* ptr ); static u16 ConvKPadKeyToDSPad(u32 mixedKey); static inline void EnterCriticalSection( void ) { (void)OSLockMutex( &sGlobalMutex ); } static void LeaveCriticalSection( void ) { (void)OSUnlockMutex( &sGlobalMutex ); } /*===========================================================================*/ // The GGID is a unique ID for MP communications assigned to each application. // You must receive a GGID assignment from Nintendo for an actual application. // Here, the GGID is used that is identical to that of the dataShare-Model demo. #define MY_GGID 0x003fff13 // MP communication settings static MPConfig sMpConfig = { mpAlloc, mpFree, 8, // threadPriority; set to a priority that is higher than that of the main thread by 4 or more MP_MODE_PARENT, // mode MY_GGID, // ggid MP_TGID_AUTO, // tgid MP_CHANNEL_AUTO, // channel; normally MP_CHANNEL_AUTO MP_LIFETIME_DEFAULT,// lifeTime; usually equal to MP_LIFETIME_DEFAULT MP_BEACON_PERIOD_AUTO, // beaconPeriod; normally MP_BEACON_PERIOD_AUTO MY_MAX_NODES, // maxNodes MY_PARENT_MAX_SIZE, // parentMaxSize MY_DS_LENGTH, // childMaxSize TRUE, // entryFlag FALSE, // multiBootFlag; usually FALSE 1, // frequency 0, // userGameInfoLength { 0, }, // userGameInfo; set MyGameInfo in the program NULL, // indicationCallbackFunction /// ... // port configuration (can be set up using MPSetPortConfig) }; // DataSharing configuration static MPDSConfig sMpdsConfig = { MY_DS_LENGTH, // dataLength MY_DS_PORT, // port (must use one of sequential communications ports 12-15) MP_PRIORITY_HIGH, // priority TRUE, // isParent; always TRUE (1 << MY_DS_NODES)-1, // aidBits (a binary number that is a row of 1's. The number of 1's is equal to MY_DS_NODES) TRUE, // isDoubleMode TRUE, // isAutoStart; always TRUE DataSharingCallback // mpdsCallback; NULL if unnecessary }; void main(void) { OSReport("test program started.\n"); Initialize(); MainLoop(); } void Initialize( void ) { /* Initialize OS and memory heap */ REXDEMOKPadInit(); REXDEMOInitScreen( FALSE ); REXDEMOSetGroundColor( black ); REXDEMOSetFontSize( 10, 20 ); REXDEMOBeginRender(); REXDEMOWaitRetrace(); (void)PADInit(); /* Initialize heap for MP library */ { void* heapAddress; heapAddress = OSGetMEM2ArenaLo(); OSSetMEM2ArenaLo( (void*)OSRoundUp32B( (u32)heapAddress + USERHEAP_SIZE ) ); sUserHeap = MEMCreateExpHeapEx( heapAddress, USERHEAP_SIZE, MEM_HEAP_OPT_THREAD_SAFE ); if( sUserHeap == NULL ) { OSHalt( "Could not create heap.\n" ); } } OSInitMutex( &sGlobalMutex ); OSInitThreadQueue( &sStateChangeThreadQueue ); // Prepare ENCContext to match the ROM font's encoding, since string display uses the ROM font // (void)ENCInitContext(&sEncContext); (void)ENCSetExternalEncoding(&sEncContext, ( OSGetFontEncode() == OS_FONT_ENCODE_SJIS ) ? (const u8*)"Shift_JIS" : (const u8*)"ISO-8859-1"); (void)ENCSetBreakType(&sEncContext, ENC_BR_KEEP); (void)ENCSetAlternativeCharacter(&sEncContext, L'?', L'?'); sState = STATE_IDLE; // Create a status management thread (void)OSCreateThread( &sStateManagementThread, StateManagementThreadFunc, NULL, (void*)((u32)sStateManagementThreadStack + STACK_SIZE), STACK_SIZE, THREAD_PRIORITY, OS_THREAD_ATTR_DETACH ); (void)OSResumeThread( &sStateManagementThread ); } void MainLoop( void ) { while (TRUE) { // Use exclusive access when touching global variables shared with other threads. EnterCriticalSection(); REXDEMOKPadRead(); LeaveCriticalSection(); if ( OSIsThreadTerminated(&sStateManagementThread) ) { // Exit the main loop because all threads have terminated. OSReport("All threads are terminated; exit from MainLoop\n"); break; } Draw(); REXDEMOWaitRetrace(); sFrameCount++; } } void Draw( void ) { s32 state; s32 i; REXDEMOBeginRender(); { // Use exclusive access when touching global variables shared with other threads. EnterCriticalSection(); // Begin exclusive access state = GetState(); switch ( state ) { case STATE_LOBBY: REXDEMOSetTextColor(green); REXDEMOPrintf(40, 8, 0, "Lobby mode"); REXDEMOSetTextColor(white); REXDEMOPrintf(4, 400, 0, "push button to start."); for (i = 0; i < MY_MAX_NODES+1; i++) { if (sConnectedFlag[i]) { char nickName[20+1]; { ENCContext convCtx; s32 dstlen, srclen; dstlen = sizeof(nickName) - 1 /* NULL termination*/; srclen = -1; // Convert up to the NULL terminator (void)NETMemSet(nickName, 0, sizeof(nickName)); (void)ENCDuplicateContext(&convCtx, &sEncContext); (void)ENCConvertFromInternalEncoding(&convCtx, (u8*)nickName, &dstlen, sConnectedNickName[i], &srclen); } REXDEMOSetTextColor(yellow); REXDEMOPrintf(40, (s16)(80 + i * 20), 0, "AID(%02d): entry %02X:%02X:%02X:%02X:%02X:%02X %s", i, sConnectedMacAddress[i][0], sConnectedMacAddress[i][1], sConnectedMacAddress[i][2], sConnectedMacAddress[i][3], sConnectedMacAddress[i][4], sConnectedMacAddress[i][5], nickName); } else { REXDEMOSetTextColor(gray); REXDEMOPrintf(40, (s16)(80 + i * 20), 0, "AID(%02d): --------", i); } } break; case STATE_COMMUNICATION: REXDEMOSetTextColor(green); REXDEMOPrintf(40, 8, 0, "Parent mode"); REXDEMOSetTextColor(white); REXDEMOPrintf(4, 400, 0, "push button to restart."); REXDEMOSetTextColor(yellow); REXDEMOPrintf(4, 30, 0, "Send: 0x%04X-0x%04X", 0, sFrameCount & 0xffff); REXDEMOPrintf(4, 52, 0, "Receive:"); for (i = 0; i < MY_MAX_NODES+1; i++) { if (sRecvFlag[i]) { REXDEMOSetTextColor(yellow); REXDEMOPrintf(40, (s16)(80 + i * 20), 0, "AID(%02d): %04X-%04X", i, MPMPToH16((u16)sRecvData[i].key), MPMPToH16((u16)(sRecvData[i].count & 0xffff)) ); } else { REXDEMOSetTextColor(red); REXDEMOPrintf(40, (s16)(80 + i * 20), 0, "No Data"); } } { static const GXColor paletteTable[] = { { 0x80, 0x80, 0x80, }, // gray { 0x00, 0xFF, 0x00, }, // green { 0xFF, 0xFF, 0x00, }, // yellow }; s16 x = 320; s16 y = 10; s16 z = -1023; for (i = 0; i < BALL_PLAYER_MAX; ++i) { REXDEMOSetTextColor(paletteTable[shared->ball[i].plt]); REXDEMOPrintf(x + shared->ball[i].x, y + shared->ball[i].y, 0, "%c", shared->ball[i].chr); } { static const u32 font[] ATTRIBUTE_ALIGN(32) = { 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, }; static const u16 pal[16] ATTRIBUTE_ALIGN(32) = { GXPackedRGB5A3(0x00, 0x00, 0x00, 0xFF), /* 0 : black */ GXPackedRGB5A3(0x80, 0x00, 0x00, 0xFF), /* 1 : dark red*/ GXPackedRGB5A3(0x00, 0x80, 0x00, 0xFF), /* 2 : dark green*/ GXPackedRGB5A3(0x80, 0x80, 0x00, 0xFF), /* 3 : dark yellow*/ GXPackedRGB5A3(0x00, 0x00, 0x80, 0xFF), /* 4 : dark blue*/ GXPackedRGB5A3(0x80, 0x00, 0x80, 0xFF), /* 5 : dark purple*/ GXPackedRGB5A3(0x00, 0x80, 0x80, 0x00), /* 6 : (clear) */ GXPackedRGB5A3(0x80, 0x80, 0x80, 0xFF), /* 7 : gray */ GXPackedRGB5A3(0xC0, 0xC0, 0xC0, 0xFF), /* 8 : light gray*/ GXPackedRGB5A3(0xFF, 0x00, 0x00, 0xFF), /* 9 : red */ GXPackedRGB5A3(0x00, 0xFF, 0x00, 0xFF), /* A : green*/ GXPackedRGB5A3(0xFF, 0xFF, 0x00, 0xFF), /* B : yellow*/ GXPackedRGB5A3(0x00, 0x00, 0xFF, 0xFF), /* C : blue*/ GXPackedRGB5A3(0xFF, 0x00, 0xFF, 0xFF), /* D : purple*/ GXPackedRGB5A3(0x00, 0xFF, 0xFF, 0xFF), /* E : cyan*/ GXPackedRGB5A3(0xFF, 0xFF, 0xFF, 0xFF), /* F : white*/ }; static GXTlutObj tlut; Mtx mtx; GXTexObj obj; GXInitTlutObj(&tlut, (void *)pal, GX_TL_RGB5A3, 16); GXLoadTlut(&tlut, GX_TLUT0); GXInitTexObjCI(&obj, (void *)font, 8, 8, GX_TF_C4, GX_CLAMP, GX_CLAMP, GX_FALSE, GX_TLUT0); GXInitTexObjLOD(&obj, GX_NEAR, GX_NEAR, 0.0f, 0.0f, 0.0f, GX_DISABLE, GX_FALSE, GX_ANISO_1); GXLoadTexObj(&obj, GX_TEXMAP4); MTXScale(mtx, 1.0f / (float)8, 1.0f / (float)8, 1.0f); GXLoadTexMtxImm(mtx, GX_TEXMTX4, GX_MTX2x4); // Temporarily change the REXDEMOBeginRender setting GXSetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_TEXMTX4); GXSetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP4, GX_COLOR0A0); GXBegin(GX_QUADS, GX_VTXFMT0, 4); { GXPosition3s16((s16) (x), (s16) (y), (s16) z); GXTexCoord2s16((s16) (0), (s16) (0)); GXPosition3s16((s16) (x + BALL_FIELD_WIDTH), (s16) (y), (s16) z); GXTexCoord2s16((s16) (8), (s16) (0)); GXPosition3s16((s16) (x + BALL_FIELD_WIDTH), (s16) (y + BALL_FIELD_HEIGHT), (s16) z); GXTexCoord2s16((s16) (8), (s16) (8)); GXPosition3s16((s16) (x), (s16) (y + BALL_FIELD_HEIGHT), (s16) z); GXTexCoord2s16((s16) (0), (s16) (8)); } GXEnd(); // Restore the REXDEMOBeginRender setting GXSetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_TEXMTX0); GXSetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR0A0); } } break; default: REXDEMOSetTextColor(green); REXDEMOPrintf(40, 8, 0, "Working..."); break; } // The link-level display is not required in the Wii Guidelines. REXDEMOSetTextColor(green); { s32 linkLevel = MPGetLinkLevel(); REXDEMOPrintf(480, 400, 0, "Link Level: %d", (linkLevel >= 0) ? linkLevel : 0); } REXDEMOSetTextColor(white); LeaveCriticalSection(); // End exclusive access } } void SetState( s32 state ) { // Use exclusive access when touching global variables shared with other threads. EnterCriticalSection(); // Begin exclusive access sState = state; LeaveCriticalSection(); // End exclusive access OSWakeupThread( &sStateChangeThreadQueue ); } inline s32 GetState( void ) { return sState; } s32 WaitForStateChange( void ) { OSSleepThread( &sStateChangeThreadQueue ); // Note that with high-priority threads, in situations such as when SetState is called multiple times, only the final change to sState will be detected. // return GetState(); } void QuitMP( void ) { sQuitFlag = TRUE; } BOOL IsQuitting( void ) { return sQuitFlag; } ///////////////////////////////////////////////////////////////////// // Management thread for game state void* StateManagementThreadFunc( void* arg ) { #pragma unused( arg ) s32 result; /////////////////////////////////////////////////////// // STATE_IDLE /////////////////////////////////////////////////////// state_idle: SetState( STATE_IDLE ); /////////////////////////////////////////////////////// // STATE_GOING_TO_LOBBY /////////////////////////////////////////////////////// state_going_to_lobby: SetState( STATE_GOING_TO_LOBBY ); result = TransToLobby(); switch ( result ) { case RESULT_OK: // go to next state break; case RESULT_ERROR: goto state_error; case RESULT_QUIT: goto state_going_to_idle; } /////////////////////////////////////////////////////// // STATE_LOBBY /////////////////////////////////////////////////////// state_lobby: SetState( STATE_LOBBY ); result = DoLobbyMode(); switch ( result ) { case RESULT_OK: // go to next state break; case RESULT_ERROR: goto state_error; case RESULT_QUIT: goto state_going_to_idle; } /////////////////////////////////////////////////////// // STATE_GOING_TO_COMMUNICATION /////////////////////////////////////////////////////// state_going_to_communication: SetState( STATE_GOING_TO_COMMUNICATION ); (void)TransToCommunication(); /////////////////////////////////////////////////////// // STATE_COMMUNICATION /////////////////////////////////////////////////////// state_communication: SetState( STATE_COMMUNICATION ); result = DoCommunicationMode(); switch ( result ) { case RESULT_OK: // go to next state goto state_going_to_idle; case RESULT_ERROR: goto state_error; case RESULT_QUIT: goto state_going_to_idle; } /////////////////////////////////////////////////////// // STATE_GOING_TO_IDLE /////////////////////////////////////////////////////// state_going_to_idle: SetState( STATE_GOING_TO_IDLE ); result = TransToIdle(); switch ( result ) { case RESULT_ERROR: goto state_error; } goto state_idle; state_error: OSReport("Error occurred.\n"); return NULL; } //////////////////////////////////////////// // Transition from the idle state to lobby mode s32 TransToLobby( void ) { s32 result; int i; // There are no other threads touching these global variables because MP communications have not yet started. // Initialization without exclusive access can be performed. for ( i = 0; i < MY_MAX_NODES+1; i++ ) { sRecvFlag[i] = FALSE; sConnectedFlag[i] = FALSE; (void)NETMemSet(sConnectedMacAddress[i], 0, MP_SIZE_MACADDRESS); (void)NETMemSet(sConnectedNickName[i], 0, sizeof(sConnectedNickName[0])); } // Configuration for this device sConnectedFlag[0] = TRUE; (void)NETGetWirelessMacAddress(sConnectedMacAddress[0]); (void)NETMemSet(sConnectedNickName[0], 0, sizeof(sConnectedNickName[0])); (void)NETMemCpy(sConnectedNickName[0], MY_NICKNAME, MY_NICKNAME_LENGTH * 2); ASSERT(MY_NICKNAME_LENGTH * 2 <= sizeof(sConnectedNickName[0]) - 2); sQuitFlag = FALSE; // All session members should be maintained locally, so initialize the game information // (Convert the input data through MP communications, and start updating the content using the same rules) InitBall(NULL, 0, 1, 2); // Initialize UserGameInfo (void)NETMemSet(&sMyGameInfo, 0, sizeof(sMyGameInfo)); sMyGameInfo.nickNameLength = MY_NICKNAME_LENGTH; /* Note that endian conversion must be performed for the data being sent to the DS*/ NETSwapAndCopyMemory16(sMyGameInfo.nickName, MY_NICKNAME, MY_NICKNAME_LENGTH*sizeof(u16)); sMyGameInfo.entryCount = 0; sMyGameInfo.periodicalCount = 0; (void)NETMemCpy( sMpConfig.userGameInfo, &sMyGameInfo, sizeof(sMyGameInfo) ); sMpConfig.userGameInfoLength = sizeof(MyGameInfo); result = MPDSInit( &sDSContext, &sMpdsConfig ); if ( result < MP_RESULT_OK ) { OSReport( "MPDSInit returns %08x\n", result ); return RESULT_ERROR; } result = MPDSSetupPortConfig( &sDSContext, &sMpConfig ); if ( result < MP_RESULT_OK ) { OSReport( "MPDSSetupPortConfig returns %08x\n", result ); return RESULT_ERROR; } // Start the MP communication state according to config // Block in order to wait for the MP state to occur. OSReport("MPStartup()\n"); result = MPStartup( &sMpConfig ); if( result != MP_RESULT_OK ) { OSReport( "MPStartup returns %08x\n", result ); return RESULT_ERROR; } // Create a thread for communications (void)OSCreateThread( &sDataSharingThread, DataSharingThreadFunc, NULL, (void*)((u32)sDataSharingThreadStack + STACK_SIZE), STACK_SIZE, THREAD_PRIORITY, 0 ); (void)OSResumeThread( &sDataSharingThread ); (void)OSCreateThread( &sUpdateGameInfoThread, UpdateGameInfoThreadFunc, NULL, (void*)((u32)sUpdateGameInfoThreadStack + STACK_SIZE), STACK_SIZE, THREAD_PRIORITY, 0 ); (void)OSResumeThread( &sUpdateGameInfoThread ); return IsQuitting() ? RESULT_QUIT : RESULT_OK; } //////////////////////////////////////////// // Transition from lobby mode to communications mode s32 TransToCommunication( void ) { s32 result; int i; // Stop accepting new connections result = MPSetEntryFlag( FALSE ); if ( result < MP_RESULT_OK ) { OSReport( "MPSetEntryFlag returns %08x\n", result ); return RESULT_ERROR; } // Update the beacon and send a notification that connections will no longer be accepted. // Block for a long period of time, until the next beacon is sent. result = MPUpdateBeacon(); if ( result < MP_RESULT_OK ) { OSReport( "MPUpdateBeacon returns %08x\n", result ); return RESULT_ERROR; } // Use exclusive access when touching global variables shared with other threads. EnterCriticalSection(); for ( i = 0; i < MY_MAX_NODES+1; i++ ) { sRecvFlag[i] = FALSE; } LeaveCriticalSection(); return IsQuitting() ? RESULT_QUIT : RESULT_OK; } //////////////////////////////////////////// // Transition to the idle state s32 TransToIdle( void ) { s32 result; // Send a termination command to the thread QuitMP(); // Ends the MP communication state // Block for a long period of time to wait for all child devices to disconnect. OSReport("MPCleanup()\n"); result = MPCleanup(); if ( result < MP_RESULT_OK ) { OSReport( "MPCleanup returns %08x\n", result ); return RESULT_ERROR; } (void)OSJoinThread( &sDataSharingThread, NULL ); (void)OSJoinThread( &sUpdateGameInfoThread, NULL ); return RESULT_OK; } //////////////////////////////////////////// // Conditions for state transitions in lobby mode s32 DoLobbyMode( void ) { BOOL fBreak = FALSE; // Accept new connections from child devices { while ( !fBreak ) { // Use exclusive access when touching global variables shared with other threads. EnterCriticalSection(); if( REXDEMOGetAnyMixedPadTrigger() & (KPAD_BUTTON_A | (PAD_BUTTON_A << 16)) ) { // Using the A Button, transition to the next state fBreak = TRUE; } LeaveCriticalSection(); VIWaitForRetrace(); } } return RESULT_OK; } //////////////////////////////////////////// // Conditions for state transitions in communication mode s32 DoCommunicationMode( void ) { BOOL fBreak = FALSE; // Communicating { while ( !fBreak ) { // Use exclusive access when touching global variables shared with other threads. EnterCriticalSection(); if( REXDEMOGetAnyMixedPadTrigger() & (KPAD_BUTTON_B | (PAD_BUTTON_B << 16)) ) { // Using the B Button, transition to the next state fBreak = TRUE; } LeaveCriticalSection(); VIWaitForRetrace(); } } return RESULT_OK; } ///////////////////////////////////////////////////////////////////// // Thread to process DataSharing void* DataSharingThreadFunc( void* arg ) { #pragma unused( arg ) s32 result; ShareData sendData; MPDSDataSet recvDataSet; BOOL fError; s32 i; s32 state; // Because the V-Blank periods are different for the Wii and the DS, the period for MP communications carried out between DS systems will not match the Wii's V-Blank period. // // Consequently, we need a reserved thread that runs at the MP communications period for DataSharing (the period with which the MPDSStep function succeeds), which is separate from the main rendering loop that is running at the Wii's V-Blank period. // // // It is also possible to perform DataSharing by using the MPDSTryStep function within the main loop, without creating a thread. However, in this case, the timing with which data is set will not match between the parent and children, and DataSharing will fail with high frequency. // // // fError = FALSE; while ( !IsQuitting() && fError == FALSE ) { // Use exclusive access when touching global variables shared with other threads. EnterCriticalSection(); // Begin exclusive access state = GetState(); switch ( state ) { case STATE_LOBBY: case STATE_COMMUNICATION: // When in a state in which DataSharing is running: // Create data to send from the current game state // Note that the Wii and DS have a different endianness. (void)NETMemSet( &sendData, 0, sizeof(sendData) ); sendData.count = MPHToMP16( (u16)sFrameCount ); // Endian conversion if ( state == STATE_LOBBY ) { sendData.command = SHARECMD_NONE; } else { sendData.command = SHARECMD_EXITLOBBY; } sendData.key = MPHToMP16(ConvKPadKeyToDSPad(REXDEMOGetAnyMixedPadHold())); sendData.level = MPGetLinkLevel(); // We must not block while maintaining exclusive access LeaveCriticalSection(); // End exclusive access // Share data using DataSharing // The MPDSStep function blocks for a long period of time until the data is complete. result = MPDSStep( &sDSContext, &sendData, &recvDataSet ); // Parse the received data and update the game state { // Use exclusive access when touching global variables shared with other threads. EnterCriticalSection(); // Begin exclusive access if ( state != GetState() ) { // The state changed while calling the MPDS[Try]Step function. // Ignore because regardless of the state, there is no disparity in data parsing at this time. // // The data that was shared must be parsed in the same way by all parents and children, so depending on the extent of the processing, it is probably best to include the state in the shared data, and break up the processing depending on the state that is included in the shared data. // // } for ( i = 0; i < MY_MAX_NODES+1; i++ ) { sRecvFlag[i] = FALSE; } if( result == MP_RESULT_OK ) { // Successfully received shared data const u8* data; // Parse the contents of the shared data for ( i = 0; i < MY_MAX_NODES+1; i++ ) { data = MPDSGetData( &sDSContext, &recvDataSet, (u32)i ); if ( data != NULL ) { // Was able to share data from the i-th sharing partner (void)NETMemCpy( (void*)&sRecvData[i], data, MY_DS_LENGTH ); sRecvFlag[i] = TRUE; } else { // There are a number of cases in which NULL is returned. // 1. Empty data is read immediately after DataSharing started // 2. The child device with the corresponding AID is not connected // (3. Some kind of internal error has occurred) // You can use this information to check if a child device is connected or disconnected. // However, in states in which children can connect freely, it is not possible to detect when a child disconnects and a separate child makes a connection immediately afterward. // // // Immediately after the start of the game, use the MPSetEntryFlag and MPUpdateBeacon functions to cut off new child connections. // } } // // // Data could have been shared between the child and the parent, so update the in-game state. // The logic for using the shared data to update the in-game state is placed here. // // If using DataSharing, you can completely synchronize the parent and children by updating the in-game state using only the data that could be obtained from the MPDS[Try]Step function. // // If the local data for the current pad input, etc. is used directly, the synchronization will be lost, so be sure to pass even your own data to the MPDS[Try]Step function once. // // // for (i = 0; i < BALL_PLAYER_MAX; ++i) { if ((i < MY_MAX_NODES+1) && sRecvFlag[i]) { InputBallKey((int)i, (int)MPMPToH16((u16)sRecvData[i].key)); } } UpdateBalls(0); } else if ( result == MP_RESULT_NO_DATA ) { // This is an error that occurs normally if the MPDSTryStep function was used and the data was not complete due to the communication status being wrong. // // // This will not occur if the MPDSStep function was used. // // Data sharing could not be achieved between the parent and children, so wait for the next frame without updating the in-game state. // // // How long this state will continue depends on the participants in the communications, so you must not carry out any operations that change the program's internal state, such as updating the coordinate data using the velocity data, or reading in random values. // // // // } else { // error OSReport("MPDSStep failed: %08x\n", result); fError = TRUE; } LeaveCriticalSection(); // End exclusive access } break; default: // if not in a communication state LeaveCriticalSection(); // End exclusive access // block and wait until the state changes (void)WaitForStateChange(); break; } } return NULL; } ///////////////////////////////////////////////////////////////////// // Thread for beacon updates void* UpdateGameInfoThreadFunc( void* arg ) { #pragma unused( arg ) while ( !IsQuitting() ) { switch ( GetState() ) { case STATE_LOBBY: case STATE_COMMUNICATION: // Update beacon contents periodically during communication sMyGameInfo.entryCount = MPCountPopulation(MPGetConnectedAIDs()); sMyGameInfo.periodicalCount++; (void)MPSetUserGameInfo(&sMyGameInfo, sizeof(sMyGameInfo)); // Apply the configured UserGameInfo to the beacon // Block for a long period of time, until the updated beacon is sent. (void)MPUpdateBeacon(); break; default: (void)WaitForStateChange(); break; } } return NULL; } ///////////////////////////////////////////////////////////////////// // Event notification callback void DataSharingCallback( s32 type, MPPortCallbackInfo* info ) { // The MPDS library forwards notifications from the MP library. // In this demo, callbacks are used to obtain detailed information about the children that have connected, but if only DataSharing is being used, there is no particular necessarily to prepare a callback function. // // // This function is called from a thread for MP library callbacks. // Because it is not an interrupt callback, it is also possible to call a thread syncrhonization function, but if blocking occurs for a long period of time, MP communications will be affected. // // Limit blocking to the minimum required to implement exclusive access. // Depending on the circumstances, you may also want to look into a method of receiving and passing data in a way that doesn't block, using OSMessageQueue or other such functions. // u32 aid; switch ( type ) { case MP_PORT_CB_TYPE_CONNECTED: // a child device has connected aid = info->connected.fromAid; EnterCriticalSection(); (void)NETMemCpy(sConnectedMacAddress[aid], info->connected.macAddress, MP_SIZE_MACADDRESS); (void)NETMemSet(sConnectedNickName[aid], 0, sizeof(sConnectedNickName[0])); /* Note that data from the DS has a different endianness.*/ NETSwapAndCopyMemory16(sConnectedNickName[aid], info->connected.ssidUserData, info->connected.ssidUserDataLength); sConnectedFlag[aid] = TRUE; LeaveCriticalSection(); break; case MP_PORT_CB_TYPE_DISCONNECTED: // a child device has disconnected aid = info->disconnected.fromAid; EnterCriticalSection(); sConnectedFlag[aid] = FALSE; (void)NETMemSet(sConnectedMacAddress[aid], 0, MP_SIZE_MACADDRESS); (void)NETMemSet(sConnectedNickName[aid], 0, sizeof(sConnectedNickName[0])); LeaveCriticalSection(); break; case MP_PORT_CB_TYPE_STARTUP: case MP_PORT_CB_TYPE_CLEANUP: case MPDS_PORT_CB_TYPE_DATASET_RECEIVED: default: // there is nothing to do break; } } /*===========================================================================*/ void* mpAlloc( u32 size ) { return MEMAllocFromExpHeapEx( sUserHeap, size, 32 ); } void mpFree( void* ptr ) { MEMFreeToExpHeap( sUserHeap, ptr ); } /*===========================================================================*/ // PAD constants for the DS #define DSPAD_BUTTON_A 0x0001 // A #define DSPAD_BUTTON_B 0x0002 // B #define DSPAD_BUTTON_SELECT 0x0004 // SELECT #define DSPAD_BUTTON_START 0x0008 // START #define DSPAD_KEY_RIGHT 0x0010 // +Control Pad, RIGHT #define DSPAD_KEY_LEFT 0x0020 // +Control Pad, LEFT #define DSPAD_KEY_UP 0x0040 // +Control Pad, UP #define DSPAD_KEY_DOWN 0x0080 // +Control Pad, DOWN #define DSPAD_BUTTON_R 0x0100 // R #define DSPAD_BUTTON_L 0x0200 // L #define DSPAD_BUTTON_X 0x0400 // X #define DSPAD_BUTTON_Y 0x0800 // Y u16 ConvKPadKeyToDSPad(u32 mixedKey) { u16 key = 0; if ( mixedKey & (KPAD_BUTTON_LEFT | (PAD_BUTTON_LEFT << 16)) ) { key |= DSPAD_KEY_LEFT; } if ( mixedKey & (KPAD_BUTTON_RIGHT | (PAD_BUTTON_RIGHT << 16)) ) { key |= DSPAD_KEY_RIGHT; } if ( mixedKey & (KPAD_BUTTON_UP | (PAD_BUTTON_UP << 16)) ) { key |= DSPAD_KEY_UP; } if ( mixedKey & (KPAD_BUTTON_DOWN | (PAD_BUTTON_DOWN << 16)) ) { key |= DSPAD_KEY_DOWN; } if( mixedKey & (KPAD_BUTTON_A | (PAD_BUTTON_A << 16)) ) { key |= DSPAD_BUTTON_A; } if( mixedKey & (KPAD_BUTTON_B | (PAD_BUTTON_B << 16)) ) { key |= DSPAD_BUTTON_A; } if( mixedKey & (KPAD_BUTTON_1 | (PAD_BUTTON_X << 16)) ) { key |= DSPAD_BUTTON_X; } if( mixedKey & (KPAD_BUTTON_2 | (PAD_BUTTON_Y << 16)) ) { key |= DSPAD_BUTTON_Y; } if( mixedKey & (KPAD_BUTTON_PLUS | (PAD_TRIGGER_R << 16)) ) { key |= DSPAD_BUTTON_R; } if( mixedKey & (KPAD_BUTTON_MINUS | (PAD_TRIGGER_L << 16)) ) { key |= DSPAD_BUTTON_L; } if( mixedKey & (KPAD_BUTTON_HOME | (PAD_BUTTON_START << 16)) ) { key |= DSPAD_BUTTON_START; } return key; }