1 /*---------------------------------------------------------------------------*
2 Project: Revolution MPDS demo
3 File: mpdsmodel.c
4
5 Copyright 2007 Nintendo. All rights reserved.
6
7 These coded instructions, statements, and computer programs contain
8 proprietary information of Nintendo of America Inc. and/or Nintendo
9 Company Ltd., and are protected by Federal copyright law. They may
10 not be disclosed to third parties or copied or duplicated in any form,
11 in whole or in part, without the prior written consent of Nintendo.
12
13 $Log: mpdsmodel.c,v $
14 Revision 1.21 2008/06/05 09:50:49 seiki_masashi
15 Added the MPWaitPseudoVBlank function.
16
17 Revision 1.20 2008/06/03 05:03:00 seiki_masashi
18 Changed to use pseudo V-Blank notifications.
19
20 Revision 1.19 2007/11/29 03:20:27 seiki_masashi
21 Added the const modifier.
22
23 Revision 1.18 2007/11/21 06:53:14 yosizaki
24 Reapplied the rolled-back content.
25
26 Revision 1.16 2007/10/27 11:20:46 seiki_masashi
27 Small fix
28
29 Revision 1.15 2007/10/26 12:15:00 yosizaki
30 Added ball display.
31
32 Revision 1.14 2007/10/26 09:31:17 seiki_masashi
33 Cleaned up the code
34
35 Revision 1.13 2007/10/26 07:29:56 seiki_masashi
36 Cleaned up the code.
37
38 Revision 1.12 2007/10/26 07:05:28 seiki_masashi
39 Revised the link-level display.
40
41 Revision 1.10 2007/10/25 03:20:53 seiki_masashi
42 Support for transformations that correspond to the ROM font encoding used by child device nicknames when they are displayed.
43
44 Revision 1.8 2007/10/24 13:50:49 seiki_masashi
45 Added a feature to display nicknames in the Lobby.
46
47 Revision 1.5 2007/10/24 01:38:51 seiki_masashi
48 Revised the method by which connection status is displayed.
49
50 Revision 1.3 2007/10/23 13:43:34 seiki_masashi
51 Revised so that pad input is sent.
52
53 Revision 1.2 2007/10/23 13:08:49 seiki_masashi
54 Changed to be multithreaded.
55
56 Revision 1.1 2007/10/23 00:05:57 seiki_masashi
57 Added the mpdsmodel demo.
58
59 $NoKeywords: $
60 *---------------------------------------------------------------------------*/
61
62 //
63 // This sample performs communications with the demos/wm/dataShare-Model demo in the NITRO-SDK.
64 //
65 // This is a demo that shows the sequence of accepting a new connection from a child device at the lobby screen, and then starting a match once new connections are no longer being accepted.
66 //
67 //
68
69 #include <string.h>
70 #include <revolution.h>
71 #include <revolution/mp.h>
72 #include <revolution/mpds.h>
73 #include <revolution/mem.h>
74 #include <revolution/enc.h>
75
76 #include "rexdemo/demokpad.h"
77 #include "rexdemo/graphic.h"
78
79 #include "ball.h"
80
81 #define USERHEAP_SIZE ( 65536 )
82
83 #define MY_NICKNAME L"Wii" // the nickname to display in the parent device selection screen on the child device
84 #define MY_NICKNAME_LENGTH 3 // the number of UTF16BE characters in MY_NICKNAME (maximum of 10)
85
86 #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)
87 #define MY_DS_NODES 16 // the number of nodes, including parent devices, that perform DataSharing (matches the dataShare-Model demo in the NITRO-SDK)
88 // for a normal application this is (MY_MAX_NODES+1)
89
90 #define MY_DS_LENGTH 12 // the shared data size per device for DataSharing
91 #define MY_DS_PORT 13 // the port number to use for DataSharing (matches the dataShare-Model demo in the NITRO-SDK)
92
93 // When DataSharing is performed, the required transmission size is the shared size per device times the number of devices, plus 4 bytes.
94 #define MY_PARENT_MAX_SIZE ( (MY_DS_LENGTH * MY_DS_NODES) + MPDS_HEADER_SIZE )
95
96 #define STACK_SIZE 8192 // thread stack size; you must also consider the amount used by interrupt processing
97 #define THREAD_PRIORITY 14 // thread priority; specify a higher priority than the main thread (16)
98
99 /*===========================================================================*/
100
101 // application-specific UserGameInfo
102 // note that processing must be done in Little Endian to match the DS
103 #pragma pack(push,1)
104 typedef struct MyGameInfo
105 {
106 u8 nickName[10 * 2];
107 u8 nickNameLength;
108 u8 entryCount;
109 u16 periodicalCount;
110 }
111 MyGameInfo;
112 #pragma pack(pop)
113
114 // shared data structure
115 // note that processing must be done in Little Endian to match the DS
116 #pragma pack(push,1)
117 typedef struct ShareData
118 {
119 u8 data:3; // For graphing
120 u8 level:2; // Signal reception strength
121 u8 command:3; // Instruction (used for switching the game states at once, etc.)
122 u16 key; // Key data
123 u32 count:24; // Frame count
124 }
125 ShareData;
126 #pragma pack(pop)
127
128 enum
129 {
130 SHARECMD_NONE = 0, // Nothing specific (normal)
131 SHARECMD_EXITLOBBY, // Signal for ending the lobby screen
132 SHARECMD_NUM
133 };
134
135 enum
136 {
137 STATE_IDLE = 0,
138 STATE_GOING_TO_LOBBY,
139 STATE_LOBBY,
140 STATE_GOING_TO_COMMUNICATION,
141 STATE_COMMUNICATION,
142 STATE_GOING_TO_IDLE
143 };
144
145 enum
146 {
147 RESULT_OK = 0,
148 RESULT_ERROR = -1,
149 RESULT_QUIT = -2
150 };
151
152 /*===========================================================================*/
153
154 static const GXColor white = { 0xFF, 0xFF, 0xFF, };
155 static const GXColor yellow = { 0xFF, 0xFF, 0x00, };
156 static const GXColor gray = { 0x80, 0x80, 0x80, };
157 static const GXColor black = { 0x00, 0x00, 0x00, };
158 static const GXColor red = { 0xFF, 0x00, 0x18 };
159 static const GXColor green = { 0x00, 0xFF, 0x00 };
160
161 static MEMHeapHandle sUserHeap;
162
163 static ShareData sRecvData[MY_MAX_NODES+1];
164 static BOOL sRecvFlag[MY_MAX_NODES+1];
165 static BOOL sConnectedFlag[MY_MAX_NODES+1];
166 static u8 sConnectedMacAddress[MY_MAX_NODES+1][MP_SIZE_MACADDRESS];
167 static u16 sConnectedNickName[MY_MAX_NODES+1][10 + 1];
168
169 static u32 sFrameCount = 0;
170 static s32 sState = STATE_IDLE;
171 static OSThreadQueue sStateChangeThreadQueue;
172 static BOOL sQuitFlag = FALSE;
173
174 static OSMutex sGlobalMutex;
175
176 static OSThread sStateManagementThread;
177 static OSThread sDataSharingThread;
178 static OSThread sUpdateGameInfoThread;
179
180 static u8 sStateManagementThreadStack[STACK_SIZE] ATTRIBUTE_ALIGN(32);
181 static u8 sDataSharingThreadStack[STACK_SIZE] ATTRIBUTE_ALIGN(32);
182 static u8 sUpdateGameInfoThreadStack[STACK_SIZE] ATTRIBUTE_ALIGN(32);
183
184 static MPDSContext sDSContext;
185 static MyGameInfo sMyGameInfo;
186 static ENCContext sEncContext;
187
188 /*===========================================================================*/
189
190 static void* StateManagementThreadFunc( void* arg );
191 static void* DataSharingThreadFunc( void* arg );
192 static void* UpdateGameInfoThreadFunc( void* arg );
193
194 static void Initialize( void );
195 static void MainLoop( void );
196
197 static void Draw( void );
198 static void SetState( s32 state );
199 static inline s32 GetState( void );
200 static s32 WaitForStateChange( void );
201 static void QuitMP( void );
202 static BOOL IsQuitting( void );
203 static s32 TransToLobby( void );
204 static s32 TransToCommunication( void );
205 static s32 TransToIdle( void );
206 static s32 DoLobbyMode( void );
207 static s32 DoCommunicationMode( void );
208 static void DataSharingCallback( s32 type, MPPortCallbackInfo* info );
209 static void IndicationCallback( s32 type, MPIndicationInfo* info );
210
211 static void* mpAlloc( u32 size );
212 static void mpFree( void* ptr );
213
214 static u16 ConvKPadKeyToDSPad(u32 mixedKey);
215
EnterCriticalSection(void)216 static inline void EnterCriticalSection( void )
217 {
218 (void)OSLockMutex( &sGlobalMutex );
219 }
220
LeaveCriticalSection(void)221 static void LeaveCriticalSection( void )
222 {
223 (void)OSUnlockMutex( &sGlobalMutex );
224 }
225
226 /*===========================================================================*/
227
228 // The GGID is a unique ID for MP communications assigned to each application.
229 // You must receive a GGID assignment from Nintendo for an actual application.
230 // Here, the GGID is used that is identical to that of the dataShare-Model demo.
231 #define MY_GGID 0x003fff13
232
233 // MP communication settings
234 static MPConfig sMpConfig =
235 {
236 mpAlloc,
237 mpFree,
238
239 8, // threadPriority; set to a priority that is higher than that of the main thread by 4 or more
240
241 MP_MODE_PARENT, // mode
242
243 MY_GGID, // ggid
244 MP_TGID_AUTO, // tgid
245
246 MP_CHANNEL_AUTO, // channel; normally MP_CHANNEL_AUTO
247
248 MP_LIFETIME_DEFAULT,// lifeTime; usually equal to MP_LIFETIME_DEFAULT
249
250 MP_BEACON_PERIOD_AUTO, // beaconPeriod; normally MP_BEACON_PERIOD_AUTO
251 MY_MAX_NODES, // maxNodes
252 MY_PARENT_MAX_SIZE, // parentMaxSize
253 MY_DS_LENGTH, // childMaxSize
254 TRUE, // entryFlag
255 FALSE, // multiBootFlag; usually FALSE
256
257 1, // frequency
258
259 0, // userGameInfoLength
260 { 0, }, // userGameInfo; set MyGameInfo in the program
261
262 NULL, // indicationCallbackFunction
263
264 /// ... // port configuration (can be set up using MPSetPortConfig)
265 };
266
267 // DataSharing configuration
268 static MPDSConfig sMpdsConfig =
269 {
270 MY_DS_LENGTH, // dataLength
271
272 MY_DS_PORT, // port (must use one of sequential communications ports 12-15)
273 MP_PRIORITY_HIGH, // priority
274
275 TRUE, // isParent; always TRUE
276 (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)
277 TRUE, // isDoubleMode
278 TRUE, // isAutoStart; always TRUE
279 DataSharingCallback // mpdsCallback; NULL if unnecessary
280 };
281
282
main(void)283 void main(void)
284 {
285 OSReport("test program started.\n");
286
287 Initialize();
288
289 MainLoop();
290 }
291
Initialize(void)292 void Initialize( void )
293 {
294 /* Initialize OS and memory heap */
295 REXDEMOKPadInit();
296 REXDEMOInitScreen( FALSE );
297 REXDEMOSetGroundColor( black );
298 REXDEMOSetFontSize( 10, 20 );
299 REXDEMOBeginRender();
300 REXDEMOWaitRetrace();
301 (void)PADInit();
302
303 /* Initialize heap for MP library */
304 {
305 void* heapAddress;
306
307 heapAddress = OSGetMEM2ArenaLo();
308 OSSetMEM2ArenaLo( (void*)OSRoundUp32B( (u32)heapAddress + USERHEAP_SIZE ) );
309 sUserHeap = MEMCreateExpHeapEx( heapAddress, USERHEAP_SIZE, MEM_HEAP_OPT_THREAD_SAFE );
310 if( sUserHeap == NULL )
311 {
312 OSHalt( "Could not create heap.\n" );
313 }
314 }
315
316 OSInitMutex( &sGlobalMutex );
317 OSInitThreadQueue( &sStateChangeThreadQueue );
318
319 // Prepare ENCContext to match the ROM font encoding because the ROM font is used to display strings.
320 //
321 (void)ENCInitContext(&sEncContext);
322 (void)ENCSetExternalEncoding(&sEncContext,
323 ( OSGetFontEncode() == OS_FONT_ENCODE_SJIS )
324 ? (const u8*)"Shift_JIS" : (const u8*)"ISO-8859-1");
325 (void)ENCSetBreakType(&sEncContext, ENC_BR_KEEP);
326 (void)ENCSetAlternativeCharacter(&sEncContext, L'?', L'?');
327
328 sState = STATE_IDLE;
329
330 // Create a status management thread
331 (void)OSCreateThread( &sStateManagementThread, StateManagementThreadFunc, NULL,
332 (void*)((u32)sStateManagementThreadStack + STACK_SIZE), STACK_SIZE,
333 THREAD_PRIORITY, OS_THREAD_ATTR_DETACH );
334 (void)OSResumeThread( &sStateManagementThread );
335 }
336
MainLoop(void)337 void MainLoop( void )
338 {
339 while (TRUE)
340 {
341 // Use exclusive access when touching global variables shared with other threads.
342 EnterCriticalSection();
343 REXDEMOKPadRead();
344 LeaveCriticalSection();
345
346 if ( OSIsThreadTerminated(&sStateManagementThread) )
347 {
348 // Exit the main loop because all threads have terminated.
349 OSReport("All threads are terminated; exit from MainLoop\n");
350 break;
351 }
352
353 Draw();
354
355 REXDEMOWaitRetrace();
356 sFrameCount++;
357 }
358 }
359
Draw(void)360 void Draw( void )
361 {
362 s32 state;
363 s32 i;
364
365 REXDEMOBeginRender();
366
367 {
368 // Use exclusive access when touching global variables shared with other threads.
369 EnterCriticalSection(); // Begin exclusive access
370 state = GetState();
371
372 switch ( state )
373 {
374 case STATE_LOBBY:
375 REXDEMOSetTextColor(green);
376 REXDEMOPrintf(40, 8, 0, "Lobby mode");
377 REXDEMOSetTextColor(white);
378 REXDEMOPrintf(4, 400, 0, "push <A> button to start.");
379
380 for (i = 0; i < MY_MAX_NODES+1; i++)
381 {
382 if (sConnectedFlag[i])
383 {
384 char nickName[20+1];
385 {
386 ENCContext convCtx;
387 s32 dstlen, srclen;
388 dstlen = sizeof(nickName) - 1 /* NULL termination */;
389 srclen = -1; // Convert up to the NULL terminator
390 (void)NETMemSet(nickName, 0, sizeof(nickName));
391 (void)ENCDuplicateContext(&convCtx, &sEncContext);
392 (void)ENCConvertFromInternalEncoding(&convCtx, (u8*)nickName, &dstlen, sConnectedNickName[i], &srclen);
393 }
394 REXDEMOSetTextColor(yellow);
395 REXDEMOPrintf(40, (s16)(80 + i * 20), 0,
396 "AID(%02d): entry %02X:%02X:%02X:%02X:%02X:%02X %s",
397 i,
398 sConnectedMacAddress[i][0],
399 sConnectedMacAddress[i][1],
400 sConnectedMacAddress[i][2],
401 sConnectedMacAddress[i][3],
402 sConnectedMacAddress[i][4],
403 sConnectedMacAddress[i][5],
404 nickName);
405 }
406 else
407 {
408 REXDEMOSetTextColor(gray);
409 REXDEMOPrintf(40, (s16)(80 + i * 20), 0, "AID(%02d): --------", i);
410 }
411 }
412 break;
413
414 case STATE_COMMUNICATION:
415 REXDEMOSetTextColor(green);
416 REXDEMOPrintf(40, 8, 0, "Parent mode");
417 REXDEMOSetTextColor(white);
418 REXDEMOPrintf(4, 400, 0, "push <B> button to restart.");
419
420 REXDEMOSetTextColor(yellow);
421 REXDEMOPrintf(4, 30, 0, "Send: 0x%04X-0x%04X", 0, sFrameCount & 0xffff);
422 REXDEMOPrintf(4, 52, 0, "Receive:");
423
424 for (i = 0; i < MY_MAX_NODES+1; i++)
425 {
426 if (sRecvFlag[i])
427 {
428 REXDEMOSetTextColor(yellow);
429 REXDEMOPrintf(40, (s16)(80 + i * 20), 0,
430 "AID(%02d): %04X-%04X",
431 i,
432 MPMPToH16((u16)sRecvData[i].key), MPMPToH16((u16)(sRecvData[i].count & 0xffff)) );
433 }
434 else
435 {
436 REXDEMOSetTextColor(red);
437 REXDEMOPrintf(40, (s16)(80 + i * 20), 0, "No Data");
438 }
439 }
440 {
441 static const GXColor paletteTable[] =
442 {
443 { 0x80, 0x80, 0x80, }, // gray
444 { 0x00, 0xFF, 0x00, }, // green
445 { 0xFF, 0xFF, 0x00, }, // yellow
446 };
447 s16 x = 320;
448 s16 y = 10;
449 s16 z = -1023;
450 for (i = 0; i < BALL_PLAYER_MAX; ++i)
451 {
452 REXDEMOSetTextColor(paletteTable[shared->ball[i].plt]);
453 REXDEMOPrintf(x + shared->ball[i].x, y + shared->ball[i].y, 0, "%c", shared->ball[i].chr);
454 }
455 {
456 static const u32 font[] ATTRIBUTE_ALIGN(32) =
457 {
458 0xFFFFFFFF,
459 0xFFFFFFFF,
460 0xFFFFFFFF,
461 0xFFFFFFFF,
462 0xFFFFFFFF,
463 0xFFFFFFFF,
464 0xFFFFFFFF,
465 0xFFFFFFFF,
466 };
467 static const u16 pal[16] ATTRIBUTE_ALIGN(32) =
468 {
469 GXPackedRGB5A3(0x00, 0x00, 0x00, 0xFF), /* 0 : black */
470 GXPackedRGB5A3(0x80, 0x00, 0x00, 0xFF), /* 1 : dark red */
471 GXPackedRGB5A3(0x00, 0x80, 0x00, 0xFF), /* 2 : dark green */
472 GXPackedRGB5A3(0x80, 0x80, 0x00, 0xFF), /* 3 : dark yellow */
473 GXPackedRGB5A3(0x00, 0x00, 0x80, 0xFF), /* 4 : dark blue */
474 GXPackedRGB5A3(0x80, 0x00, 0x80, 0xFF), /* 5 : dark purple */
475 GXPackedRGB5A3(0x00, 0x80, 0x80, 0x00), /* 6 : (clear) */
476 GXPackedRGB5A3(0x80, 0x80, 0x80, 0xFF), /* 7 : gray */
477 GXPackedRGB5A3(0xC0, 0xC0, 0xC0, 0xFF), /* 8 : light gray */
478 GXPackedRGB5A3(0xFF, 0x00, 0x00, 0xFF), /* 9 : red */
479 GXPackedRGB5A3(0x00, 0xFF, 0x00, 0xFF), /* A: green */
480 GXPackedRGB5A3(0xFF, 0xFF, 0x00, 0xFF), /* B: yellow */
481 GXPackedRGB5A3(0x00, 0x00, 0xFF, 0xFF), /* C: blue */
482 GXPackedRGB5A3(0xFF, 0x00, 0xFF, 0xFF), /* D : purple */
483 GXPackedRGB5A3(0x00, 0xFF, 0xFF, 0xFF), /* E : cyan */
484 GXPackedRGB5A3(0xFF, 0xFF, 0xFF, 0xFF), /* F : white */
485 };
486 static GXTlutObj tlut;
487 Mtx mtx;
488 GXTexObj obj;
489 GXInitTlutObj(&tlut, (void *)pal, GX_TL_RGB5A3, 16);
490 GXLoadTlut(&tlut, GX_TLUT0);
491 GXInitTexObjCI(&obj, (void *)font,
492 8, 8, GX_TF_C4,
493 GX_CLAMP, GX_CLAMP, GX_FALSE, GX_TLUT0);
494 GXInitTexObjLOD(&obj, GX_NEAR, GX_NEAR,
495 0.0f, 0.0f, 0.0f, GX_DISABLE,
496 GX_FALSE, GX_ANISO_1);
497 GXLoadTexObj(&obj, GX_TEXMAP4);
498 MTXScale(mtx, 1.0f / (float)8, 1.0f / (float)8, 1.0f);
499 GXLoadTexMtxImm(mtx, GX_TEXMTX4, GX_MTX2x4);
500 // Temporarily change the REXDEMOBeginRender setting
501 GXSetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_TEXMTX4);
502 GXSetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP4, GX_COLOR0A0);
503 GXBegin(GX_QUADS, GX_VTXFMT0, 4);
504 {
505 GXPosition3s16((s16) (x), (s16) (y), (s16) z);
506 GXTexCoord2s16((s16) (0), (s16) (0));
507 GXPosition3s16((s16) (x + BALL_FIELD_WIDTH), (s16) (y), (s16) z);
508 GXTexCoord2s16((s16) (8), (s16) (0));
509 GXPosition3s16((s16) (x + BALL_FIELD_WIDTH), (s16) (y + BALL_FIELD_HEIGHT), (s16) z);
510 GXTexCoord2s16((s16) (8), (s16) (8));
511 GXPosition3s16((s16) (x), (s16) (y + BALL_FIELD_HEIGHT), (s16) z);
512 GXTexCoord2s16((s16) (0), (s16) (8));
513 }
514 GXEnd();
515 // Restore the REXDEMOBeginRender setting
516 GXSetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_TEXMTX0);
517 GXSetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR0A0);
518 }
519 }
520
521 break;
522
523 default:
524 REXDEMOSetTextColor(green);
525 REXDEMOPrintf(40, 8, 0, "Working...");
526 break;
527 }
528
529 // The link-level display is not required in the Wii Guidelines.
530 REXDEMOSetTextColor(green);
531 {
532 s32 linkLevel = MPGetLinkLevel();
533 REXDEMOPrintf(480, 400, 0, "Link Level: %d", (linkLevel >= 0) ? linkLevel : 0);
534 }
535
536 REXDEMOSetTextColor(white);
537
538 LeaveCriticalSection(); // End exclusive access
539 }
540 }
541
SetState(s32 state)542 void SetState( s32 state )
543 {
544 // Use exclusive access when touching global variables shared with other threads.
545 EnterCriticalSection(); // Begin exclusive access
546 sState = state;
547 LeaveCriticalSection(); // End exclusive access
548 OSWakeupThread( &sStateChangeThreadQueue );
549 }
550
GetState(void)551 inline s32 GetState( void )
552 {
553 return sState;
554 }
555
WaitForStateChange(void)556 s32 WaitForStateChange( void )
557 {
558 OSSleepThread( &sStateChangeThreadQueue );
559 // Note that only the final change to sState will be detected in situations such as when SetState is called multiple times in high-priority threads.
560 //
561 return GetState();
562 }
563
QuitMP(void)564 void QuitMP( void )
565 {
566 sQuitFlag = TRUE;
567 }
568
IsQuitting(void)569 BOOL IsQuitting( void )
570 {
571 return sQuitFlag;
572 }
573
574 /////////////////////////////////////////////////////////////////////
575 // Management thread for game state
StateManagementThreadFunc(void * arg)576 void* StateManagementThreadFunc( void* arg )
577 {
578 #pragma unused( arg )
579 s32 result;
580
581 ///////////////////////////////////////////////////////
582 // STATE_IDLE
583 ///////////////////////////////////////////////////////
584 state_idle:
585 SetState( STATE_IDLE );
586
587 ///////////////////////////////////////////////////////
588 // STATE_GOING_TO_LOBBY
589 ///////////////////////////////////////////////////////
590 state_going_to_lobby:
591 SetState( STATE_GOING_TO_LOBBY );
592
593 result = TransToLobby();
594
595 switch ( result )
596 {
597 case RESULT_OK:
598 // go to next state
599 break;
600
601 case RESULT_ERROR:
602 goto state_error;
603
604 case RESULT_QUIT:
605 goto state_going_to_idle;
606 }
607
608 ///////////////////////////////////////////////////////
609 // STATE_LOBBY
610 ///////////////////////////////////////////////////////
611 state_lobby:
612 SetState( STATE_LOBBY );
613
614 result = DoLobbyMode();
615
616 switch ( result )
617 {
618 case RESULT_OK:
619 // go to next state
620 break;
621
622 case RESULT_ERROR:
623 goto state_error;
624
625 case RESULT_QUIT:
626 goto state_going_to_idle;
627 }
628
629 ///////////////////////////////////////////////////////
630 // STATE_GOING_TO_COMMUNICATION
631 ///////////////////////////////////////////////////////
632 state_going_to_communication:
633 SetState( STATE_GOING_TO_COMMUNICATION );
634
635 (void)TransToCommunication();
636
637 ///////////////////////////////////////////////////////
638 // STATE_COMMUNICATION
639 ///////////////////////////////////////////////////////
640 state_communication:
641 SetState( STATE_COMMUNICATION );
642
643 result = DoCommunicationMode();
644
645 switch ( result )
646 {
647 case RESULT_OK:
648 // go to next state
649 goto state_going_to_idle;
650
651 case RESULT_ERROR:
652 goto state_error;
653
654 case RESULT_QUIT:
655 goto state_going_to_idle;
656 }
657
658 ///////////////////////////////////////////////////////
659 // STATE_GOING_TO_IDLE
660 ///////////////////////////////////////////////////////
661 state_going_to_idle:
662 SetState( STATE_GOING_TO_IDLE );
663
664 result = TransToIdle();
665
666 switch ( result )
667 {
668 case RESULT_ERROR:
669 goto state_error;
670 }
671
672 goto state_idle;
673
674 state_error:
675 OSReport("Error occurred.\n");
676
677 return NULL;
678 }
679
680 ////////////////////////////////////////////
681 // Transition from the idle state to lobby mode
TransToLobby(void)682 s32 TransToLobby( void )
683 {
684 s32 result;
685 int i;
686
687 // There are no other threads touching these global variables because MP communications have not yet started.
688 // Initialization without exclusive access can be performed.
689 for ( i = 0; i < MY_MAX_NODES+1; i++ )
690 {
691 sRecvFlag[i] = FALSE;
692 sConnectedFlag[i] = FALSE;
693 (void)NETMemSet(sConnectedMacAddress[i], 0, MP_SIZE_MACADDRESS);
694 (void)NETMemSet(sConnectedNickName[i], 0, sizeof(sConnectedNickName[0]));
695 }
696 // Configuration for this device
697 sConnectedFlag[0] = TRUE;
698 (void)NETGetWirelessMacAddress(sConnectedMacAddress[0]);
699 (void)NETMemSet(sConnectedNickName[0], 0, sizeof(sConnectedNickName[0]));
700 (void)NETMemCpy(sConnectedNickName[0], MY_NICKNAME, MY_NICKNAME_LENGTH * 2);
701 ASSERT(MY_NICKNAME_LENGTH * 2 <= sizeof(sConnectedNickName[0]) - 2);
702
703 sQuitFlag = FALSE;
704
705
706 // All session members should be maintained locally, so initialize the game information
707 // (Convert the input data through MP communications, and start updating the content using the same rules)
708 InitBall(NULL, 0, 1, 2);
709
710
711 // Initialize UserGameInfo
712 (void)NETMemSet(&sMyGameInfo, 0, sizeof(sMyGameInfo));
713 sMyGameInfo.nickNameLength = MY_NICKNAME_LENGTH;
714 /* Note that endian conversion must be performed for the data being sent to the DS */
715 NETSwapAndCopyMemory16(sMyGameInfo.nickName, MY_NICKNAME, MY_NICKNAME_LENGTH*sizeof(u16));
716 sMyGameInfo.entryCount = 0;
717 sMyGameInfo.periodicalCount = 0;
718 (void)NETMemCpy( sMpConfig.userGameInfo, &sMyGameInfo, sizeof(sMyGameInfo) );
719 sMpConfig.userGameInfoLength = sizeof(MyGameInfo);
720
721 MPSetIndicationConfig( &sMpConfig, IndicationCallback );
722
723 result = MPDSInit( &sDSContext, &sMpdsConfig );
724 if ( result < MP_RESULT_OK )
725 {
726 OSReport( "MPDSInit returns %08x\n", result );
727 return RESULT_ERROR;
728 }
729
730 result = MPDSSetupPortConfig( &sDSContext, &sMpConfig );
731 if ( result < MP_RESULT_OK )
732 {
733 OSReport( "MPDSSetupPortConfig returns %08x\n", result );
734 return RESULT_ERROR;
735 }
736
737 // Start the MP communication state according to config
738 // Block in order to wait for the MP state to occur.
739 OSReport("MPStartup()\n");
740 result = MPStartup( &sMpConfig );
741 if( result != MP_RESULT_OK )
742 {
743 OSReport( "MPStartup returns %08x\n", result );
744 return RESULT_ERROR;
745 }
746
747 // Create a thread for communications
748 (void)OSCreateThread( &sDataSharingThread, DataSharingThreadFunc, NULL,
749 (void*)((u32)sDataSharingThreadStack + STACK_SIZE), STACK_SIZE,
750 THREAD_PRIORITY, 0 );
751 (void)OSResumeThread( &sDataSharingThread );
752 (void)OSCreateThread( &sUpdateGameInfoThread, UpdateGameInfoThreadFunc, NULL,
753 (void*)((u32)sUpdateGameInfoThreadStack + STACK_SIZE), STACK_SIZE,
754 THREAD_PRIORITY, 0 );
755 (void)OSResumeThread( &sUpdateGameInfoThread );
756
757 return IsQuitting() ? RESULT_QUIT : RESULT_OK;
758 }
759
760 ////////////////////////////////////////////
761 // Transition from lobby mode to communications mode
TransToCommunication(void)762 s32 TransToCommunication( void )
763 {
764 s32 result;
765 int i;
766
767 // Stop accepting new connections
768 result = MPSetEntryFlag( FALSE );
769 if ( result < MP_RESULT_OK )
770 {
771 OSReport( "MPSetEntryFlag returns %08x\n", result );
772 return RESULT_ERROR;
773 }
774
775 // Update the beacon and send a notification that connections will no longer be accepted.
776 // Block for a long period of time, until the next beacon is sent.
777 result = MPUpdateBeacon();
778 if ( result < MP_RESULT_OK )
779 {
780 OSReport( "MPUpdateBeacon returns %08x\n", result );
781 return RESULT_ERROR;
782 }
783
784 // Use exclusive access when touching global variables shared with other threads.
785 EnterCriticalSection();
786 for ( i = 0; i < MY_MAX_NODES+1; i++ )
787 {
788 sRecvFlag[i] = FALSE;
789 }
790 LeaveCriticalSection();
791
792 return IsQuitting() ? RESULT_QUIT : RESULT_OK;
793 }
794
795 ////////////////////////////////////////////
796 // Transition to the idle state
TransToIdle(void)797 s32 TransToIdle( void )
798 {
799 s32 result;
800
801 // Send a termination command to the thread
802 QuitMP();
803
804 // Ends the MP communication state
805 // Block for a long period of time to wait for all child devices to disconnect.
806 OSReport("MPCleanup()\n");
807 result = MPCleanup();
808 if ( result < MP_RESULT_OK )
809 {
810 OSReport( "MPCleanup returns %08x\n", result );
811 return RESULT_ERROR;
812 }
813
814 (void)OSJoinThread( &sDataSharingThread, NULL );
815 (void)OSJoinThread( &sUpdateGameInfoThread, NULL );
816
817 return RESULT_OK;
818 }
819
820 ////////////////////////////////////////////
821 // Conditions for state transitions in lobby mode
DoLobbyMode(void)822 s32 DoLobbyMode( void )
823 {
824 BOOL fBreak = FALSE;
825
826 // Accept new connections from child devices
827 {
828 while ( !fBreak )
829 {
830 // Use exclusive access when touching global variables shared with other threads.
831 EnterCriticalSection();
832 if( REXDEMOGetAnyMixedPadTrigger() & (KPAD_BUTTON_A | (PAD_BUTTON_A << 16)) )
833 {
834 // Using the A Button, transition to the next state
835 fBreak = TRUE;
836 }
837 LeaveCriticalSection();
838
839 VIWaitForRetrace();
840 }
841 }
842
843 return RESULT_OK;
844 }
845
846 ////////////////////////////////////////////
847 // Conditions for state transitions in communication mode
DoCommunicationMode(void)848 s32 DoCommunicationMode( void )
849 {
850 BOOL fBreak = FALSE;
851
852 // Communicating
853 {
854 while ( !fBreak )
855 {
856 // Use exclusive access when touching global variables shared with other threads.
857 EnterCriticalSection();
858 if( REXDEMOGetAnyMixedPadTrigger() & (KPAD_BUTTON_B | (PAD_BUTTON_B << 16)) )
859 {
860 // Using the B Button, transition to the next state
861 fBreak = TRUE;
862 }
863 LeaveCriticalSection();
864
865 VIWaitForRetrace();
866 }
867 }
868
869 return RESULT_OK;
870 }
871
872
873 /////////////////////////////////////////////////////////////////////
874 // Thread to process DataSharing
DataSharingThreadFunc(void * arg)875 void* DataSharingThreadFunc( void* arg )
876 {
877 #pragma unused( arg )
878 s32 result;
879 ShareData sendData;
880 MPDSDataSet recvDataSet;
881 BOOL fError;
882 s32 i;
883 s32 state;
884
885 // Because the Wii and DS have different V-Blank cycles, the Wii's V-Blank period will not match the period for MP communications carried out between DS systems.
886 //
887 // Consequently, DataSharing requires a reserved thread that runs at the MP communications cycle (the cycle at which the MPDSStep function succeeds); this is separate from the main rendering loop that runs at the Wii's V-Blank period.
888 //
889 //
890
891 // 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.
892 //
893 //
894 //
895
896 fError = FALSE;
897
898 while ( !IsQuitting() && fError == FALSE )
899 {
900 // Use exclusive access when touching global variables shared with other threads.
901 EnterCriticalSection(); // Begin exclusive access
902
903 state = GetState();
904
905 switch ( state )
906 {
907 case STATE_LOBBY:
908 case STATE_COMMUNICATION:
909 // When in a state in which DataSharing is running:
910
911 // Create data to send from the current game state
912 // Note that the Wii and DS have a different endianness.
913 (void)NETMemSet( &sendData, 0, sizeof(sendData) );
914 sendData.count = MPHToMP16( (u16)sFrameCount ); // Endian conversion
915 if ( state == STATE_LOBBY )
916 {
917 sendData.command = SHARECMD_NONE;
918 }
919 else
920 {
921 sendData.command = SHARECMD_EXITLOBBY;
922 }
923 sendData.key = MPHToMP16(ConvKPadKeyToDSPad(REXDEMOGetAnyMixedPadHold()));
924 sendData.level = MPGetLinkLevel();
925
926 // We must not block while maintaining exclusive access
927 LeaveCriticalSection(); // End exclusive access
928
929 // Share data using DataSharing
930 // The MPDSStep function blocks for a long period of time until the data is complete.
931 result = MPDSStep( &sDSContext, &sendData, &recvDataSet );
932
933 // Parse the received data and update the game state
934 {
935 // Use exclusive access when touching global variables shared with other threads.
936 EnterCriticalSection(); // Begin exclusive access
937
938 if ( state != GetState() )
939 {
940 // The state changed while calling the MPDS[Try]Step function.
941 // Ignore because regardless of the state, there is no disparity in data parsing at this time.
942 //
943 // All parents and children must parse the shared data in the same way, so depending on the extent of processing, it is probably best to include the state in the shared data and branch processing according to that state.
944 //
945 //
946 }
947
948 for ( i = 0; i < MY_MAX_NODES+1; i++ )
949 {
950 sRecvFlag[i] = FALSE;
951 }
952
953 if( result == MP_RESULT_OK )
954 {
955 // Successfully received shared data
956 const u8* data;
957
958 // Parse the contents of the shared data
959 for ( i = 0; i < MY_MAX_NODES+1; i++ )
960 {
961 data = MPDSGetData( &sDSContext, &recvDataSet, (u32)i );
962 if ( data != NULL )
963 {
964 // Was able to share data from the i-th sharing partner
965 (void)NETMemCpy( (void*)&sRecvData[i], data, MY_DS_LENGTH );
966 sRecvFlag[i] = TRUE;
967 }
968 else
969 {
970 // There are a number of cases in which NULL is returned.
971 // 1. Empty data is read immediately after DataSharing started
972 // 2. The child device with the corresponding AID is not connected
973 // (3. Some kind of internal error has occurred)
974
975 // You can use this information to check if a child device is connected or disconnected.
976 // However, in states where child devices can connect freely, it is not possible to detect a child that has connected immediately after another one has disconnected.
977 //
978 //
979 // Use the MPSetEntryFlag and MPUpdateBeacon functions to cut off new child connections after the game starts.
980 //
981 }
982 }
983
984 //
985 //
986 // Data could have been shared between the child and the parent, so update the in-game state.
987 // The logic for using the shared data to update the in-game state is placed here.
988 //
989 // 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 functions.
990 //
991 // Synchronization will be lost if the local data such as the current pad input is used directly, so be sure to pass even your own data to the MPDS[Try]Step function once.
992 //
993 //
994 //
995
996 for (i = 0; i < BALL_PLAYER_MAX; ++i)
997 {
998 if ((i < MY_MAX_NODES+1) && sRecvFlag[i])
999 {
1000 InputBallKey((int)i, (int)MPMPToH16((u16)sRecvData[i].key));
1001 }
1002 }
1003 UpdateBalls(0);
1004
1005 }
1006 else if ( result == MP_RESULT_NO_DATA )
1007 {
1008 // This is an error that occurs normally if the MPDSTryStep function was used and the data was not complete due to a bad communication state or other cause.
1009 //
1010 //
1011 // This will not occur if the MPDSStep function was used.
1012
1013 //
1014 // Data sharing could not be achieved between the parent and children, so wait for the next frame without updating the in-game state.
1015 //
1016 //
1017 // The length of this state may be different for different participants, so you must not carry out any operations that change the program's internal state, such as updating coordinate data using velocity data, or reading in random values.
1018 //
1019 //
1020 //
1021 //
1022 }
1023 else
1024 {
1025 // error
1026 OSReport("MPDSStep failed: %08x\n", result);
1027 fError = TRUE;
1028 }
1029
1030 LeaveCriticalSection(); // End exclusive access
1031 }
1032 break;
1033
1034 default:
1035 // if not in a communication state
1036 LeaveCriticalSection(); // End exclusive access
1037
1038 // block and wait until the state changes
1039 (void)WaitForStateChange();
1040 break;
1041 }
1042
1043 // Synchronize the loop with the pseudo-VBLANK period, which is synchronized by the child devices.
1044 // Doing this will reduce communication latency and apply the most recent possible parent device state to transmitted data that is received by child devices.
1045 //
1046 (void)MPWaitPseudoVBlank();
1047 }
1048
1049 return NULL;
1050 }
1051
1052
1053 /////////////////////////////////////////////////////////////////////
1054 // Thread for beacon updates
UpdateGameInfoThreadFunc(void * arg)1055 void* UpdateGameInfoThreadFunc( void* arg )
1056 {
1057 #pragma unused( arg )
1058 while ( !IsQuitting() )
1059 {
1060 switch ( GetState() )
1061 {
1062 case STATE_LOBBY:
1063 case STATE_COMMUNICATION:
1064 // Update beacon contents periodically during communication
1065 sMyGameInfo.entryCount = MPCountPopulation(MPGetConnectedAIDs());
1066 sMyGameInfo.periodicalCount++;
1067 (void)MPSetUserGameInfo(&sMyGameInfo, sizeof(sMyGameInfo));
1068
1069 // Apply the configured UserGameInfo to the beacon
1070 // Block for a long period of time, until the updated beacon is sent.
1071 (void)MPUpdateBeacon();
1072 break;
1073
1074 default:
1075 (void)WaitForStateChange();
1076 break;
1077 }
1078 }
1079
1080 return NULL;
1081 }
1082
1083 /////////////////////////////////////////////////////////////////////
1084 // Event notification callback
DataSharingCallback(s32 type,MPPortCallbackInfo * info)1085 void DataSharingCallback( s32 type, MPPortCallbackInfo* info )
1086 {
1087 // The MPDS library forwards notifications from the MP library.
1088 // In this demo, callbacks are used to obtain detailed information about the children that have connected, but if only DataSharing is being used, it is not necessary to prepare a callback function.
1089 //
1090 //
1091
1092 // This function is called from a thread for MP library callbacks.
1093 // Because it is not an interrupt callback, it is also possible to call a thread synchronization function, but MP communications will be affected if blocking occurs for a long period of time.
1094 //
1095 // Limit blocking to the minimum required to implement exclusive access.
1096 // Depending on the circumstances, you may also want to look into a non-blocking method of receiving and passing data using OSMessageQueue or other such functions.
1097 //
1098
1099 u32 aid;
1100
1101 switch ( type )
1102 {
1103 case MP_PORT_CB_TYPE_CONNECTED:
1104 // a child device has connected
1105 aid = info->connected.fromAid;
1106
1107 EnterCriticalSection();
1108 (void)NETMemCpy(sConnectedMacAddress[aid], info->connected.macAddress, MP_SIZE_MACADDRESS);
1109 (void)NETMemSet(sConnectedNickName[aid], 0, sizeof(sConnectedNickName[0]));
1110 /* Note that data from the DS has a different endianness. */
1111 NETSwapAndCopyMemory16(sConnectedNickName[aid], info->connected.ssidUserData, info->connected.ssidUserDataLength);
1112 sConnectedFlag[aid] = TRUE;
1113 LeaveCriticalSection();
1114
1115 break;
1116
1117 case MP_PORT_CB_TYPE_DISCONNECTED:
1118 // a child device has disconnected
1119 aid = info->disconnected.fromAid;
1120
1121 EnterCriticalSection();
1122 sConnectedFlag[aid] = FALSE;
1123 (void)NETMemSet(sConnectedMacAddress[aid], 0, MP_SIZE_MACADDRESS);
1124 (void)NETMemSet(sConnectedNickName[aid], 0, sizeof(sConnectedNickName[0]));
1125 LeaveCriticalSection();
1126
1127 break;
1128
1129 case MP_PORT_CB_TYPE_STARTUP:
1130 case MP_PORT_CB_TYPE_CLEANUP:
1131 case MPDS_PORT_CB_TYPE_DATASET_RECEIVED:
1132 default:
1133 // there is nothing to do
1134 break;
1135 }
1136 }
1137
IndicationCallback(s32 type,MPIndicationInfo * info)1138 void IndicationCallback( s32 type, MPIndicationInfo* info )
1139 {
1140 // This callback is used to accept general notifications of all types sent by the MP library.
1141 // Fatal errors can also be handled by processing return values from library functions, so callback functions do not necessarily need to be prepared.
1142 //
1143
1144 // This function is called from a thread for MP library callbacks.
1145 // Because it is not an interrupt callback, it is also possible to call a thread synchronization function, but MP communications will be affected if blocking occurs for a long period of time.
1146 //
1147 // Limit blocking to the minimum required to implement exclusive access.
1148 // Depending on the circumstances, you may also want to look into a non-blocking method of receiving and passing data using OSMessageQueue or other such functions.
1149 //
1150
1151 switch ( type )
1152 {
1153 case MP_INDICATION_CB_TYPE_FATAL_ERROR:
1154 // A fatal error occurred
1155
1156 // A notification is sent when an irrecoverable error (such as a memory allocation error) has occurred internally.
1157 OSReport("Fatal Error occurred: error=%d\n", info->value);
1158
1159 break;
1160
1161 case MP_INDICATION_CB_TYPE_PSEUDO_VBLANK:
1162 // Pseudo V-Blank notification
1163
1164 // By specifying MP_MODE_PARENT | MP_MODE_ENABLE_PSEUDO_VBLANK_INDICATION for MPConfig.mode, it is possible to receive notifications in the pseudo V-Blank timing synchronized to by child devices.
1165 //
1166 //
1167 // Not used in this demo.
1168
1169 break;
1170
1171 default:
1172 // there is nothing to do
1173 break;
1174 }
1175 }
1176
1177 /*===========================================================================*/
1178
mpAlloc(u32 size)1179 void* mpAlloc( u32 size )
1180 {
1181 OSReport("allocated %d bytes\n", size);
1182 return MEMAllocFromExpHeapEx( sUserHeap, size, 32 );
1183 }
1184
mpFree(void * ptr)1185 void mpFree( void* ptr )
1186 {
1187 MEMFreeToExpHeap( sUserHeap, ptr );
1188 }
1189
1190 /*===========================================================================*/
1191
1192 // PAD constants for the DS
1193 #define DSPAD_BUTTON_A 0x0001 // A
1194 #define DSPAD_BUTTON_B 0x0002 // B
1195 #define DSPAD_BUTTON_SELECT 0x0004 // SELECT
1196 #define DSPAD_BUTTON_START 0x0008 // START
1197 #define DSPAD_KEY_RIGHT 0x0010 // +Control Pad, RIGHT
1198 #define DSPAD_KEY_LEFT 0x0020 // +Control Pad, LEFT
1199 #define DSPAD_KEY_UP 0x0040 // +Control Pad, UP
1200 #define DSPAD_KEY_DOWN 0x0080 // +Control Pad, DOWN
1201 #define DSPAD_BUTTON_R 0x0100 // R
1202 #define DSPAD_BUTTON_L 0x0200 // L
1203 #define DSPAD_BUTTON_X 0x0400 // X
1204 #define DSPAD_BUTTON_Y 0x0800 // Y
1205
ConvKPadKeyToDSPad(u32 mixedKey)1206 u16 ConvKPadKeyToDSPad(u32 mixedKey)
1207 {
1208 u16 key = 0;
1209
1210 if ( mixedKey & (KPAD_BUTTON_LEFT | (PAD_BUTTON_LEFT << 16)) )
1211 {
1212 key |= DSPAD_KEY_LEFT;
1213 }
1214 if ( mixedKey & (KPAD_BUTTON_RIGHT | (PAD_BUTTON_RIGHT << 16)) )
1215 {
1216 key |= DSPAD_KEY_RIGHT;
1217 }
1218 if ( mixedKey & (KPAD_BUTTON_UP | (PAD_BUTTON_UP << 16)) )
1219 {
1220 key |= DSPAD_KEY_UP;
1221 }
1222 if ( mixedKey & (KPAD_BUTTON_DOWN | (PAD_BUTTON_DOWN << 16)) )
1223 {
1224 key |= DSPAD_KEY_DOWN;
1225 }
1226 if( mixedKey & (KPAD_BUTTON_A | (PAD_BUTTON_A << 16)) )
1227 {
1228 key |= DSPAD_BUTTON_A;
1229 }
1230 if( mixedKey & (KPAD_BUTTON_B | (PAD_BUTTON_B << 16)) )
1231 {
1232 key |= DSPAD_BUTTON_A;
1233 }
1234 if( mixedKey & (KPAD_BUTTON_1 | (PAD_BUTTON_X << 16)) )
1235 {
1236 key |= DSPAD_BUTTON_X;
1237 }
1238 if( mixedKey & (KPAD_BUTTON_2 | (PAD_BUTTON_Y << 16)) )
1239 {
1240 key |= DSPAD_BUTTON_Y;
1241 }
1242 if( mixedKey & (KPAD_BUTTON_PLUS | (PAD_TRIGGER_R << 16)) )
1243 {
1244 key |= DSPAD_BUTTON_R;
1245 }
1246 if( mixedKey & (KPAD_BUTTON_MINUS | (PAD_TRIGGER_L << 16)) )
1247 {
1248 key |= DSPAD_BUTTON_L;
1249 }
1250 if( mixedKey & (KPAD_BUTTON_HOME | (PAD_BUTTON_START << 16)) )
1251 {
1252 key |= DSPAD_BUTTON_START;
1253 }
1254
1255 return key;
1256 }
1257