1 /*---------------------------------------------------------------------------*
2   Project:  Horizon
3   File:     FsSampleStreamingFrame.cpp
4 
5   Copyright (C)2009-2012 Nintendo Co., Ltd.  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   $Rev: 46547 $
14  *---------------------------------------------------------------------------*/
15 
16 #include <nn.h>
17 #include "demo.h"
18 
19 #include "../Common/FsSampleCommon.h"
20 #include "FsSampleStreaming.h"
21 
22 using namespace nn;
23 namespace sample { namespace fs { namespace streaming {
24 
25 enum ENUM_TASK
26 {
27     BG_TASK_NONE,
28     BG_TASK_SAVE,
29     TASK_REVERT,
30     BG_TASK_LOAD,
31 
32     ENUM_TASK_NUM
33 } s_BgTask;
34 
35 enum ENUM_REC_STATE
36 {
37     REC_STATE_NONE,
38     REC_STATE_RUNNING,
39     REC_STATE_SUCCEEDED,
40     REC_STATE_FAILED,
41 
42     ENUM_REC_STATE_NUM
43 } s_RecState;
44 
45 enum ENUM_PLAY_STATE
46 {
47     PLAY_STATE_NONE,
48     PLAY_STATE_RUNNING,
49     PLAY_STATE_SUCCEEDED,
50     PLAY_STATE_STOPPED,
51     PLAY_STATE_FAILED,
52 
53     ENUM_PLAY_STATE_NUM
54 } s_PlayState;
55 
56 enum ENUM_BG_STATE
57 {
58     STATE_BG_NONE,
59     STATE_BG_SAVE,
60     STATE_BG_SAVE_SUCCEEDED,
61     STATE_BG_SAVE_FAILED,
62     STATE_BG_LOAD,
63     STATE_BG_LOAD_SUCCEEDED,
64     STATE_BG_LOAD_FAILED,
65 
66     ENUM_BG_STATE_NUM
67 } s_BgState;
68 
69 bool s_IsWorkerThreadAlive;
70 nn::os::Event s_WorkerEvent;
71 nn::os::Thread s_WorkerThread;
72 
73 int s_RecordedFrameNum = 0;
74 int s_DroppedFrameNum = 0;
75 
76 int s_BgSaveData = 0;
77 int s_LoadFileNum;
78 int s_LoadFileIndex;
79 
80 bit8 s_MemoBuffer[MEMO_BUFFER_SIZE];
81 
82 static nn::hid::CTR::TouchPanelStatus s_TouchPanelStatus;
83 static nn::hid::CTR::TouchPanelStatus s_PreviousTouchPanel;
84 
85 GLuint s_TextureId = 0;
86 
DrawPoint(const u16 orgX,const u16 orgY)87 void DrawPoint(const u16 orgX, const u16 orgY)
88 {
89     if (MEMO_X <= orgX && orgX < MEMO_X + MEMO_WIDTH &&
90         MEMO_Y <= orgY && orgY < MEMO_Y + MEMO_HEIGHT)
91     {
92         // When mapping the memo content to a texture, you must convert from a linear format to a block format.
93         // With this sample, this conversion process is performed in advance when there is touch input, and the converted data is used internally.
94         u16 x = orgX - MEMO_X;
95         u16 y = orgY - MEMO_Y;
96         u32 id = y / TEXTURE_BLOCK_SIZE * MEMO_WIDTH * TEXTURE_BLOCK_SIZE + x / TEXTURE_BLOCK_SIZE * TEXTURE_BLOCK_SIZE * TEXTURE_BLOCK_SIZE +
97             (((x >> 0) & 1) << 0) +
98             (((x >> 1) & 1) << 2) +
99             (((x >> 2) & 1) << 4) +
100             (((y >> 0) & 1) << 1) +
101             (((y >> 1) & 1) << 3) +
102             (((y >> 2) & 1) << 5);
103 
104         s_MemoBuffer[id / 8] |= 1 << (id % 8);
105     }
106 }
107 
DrawLine(const nn::hid::CTR::TouchPanelStatus & now,const nn::hid::CTR::TouchPanelStatus & previous)108 void DrawLine(const nn::hid::CTR::TouchPanelStatus &now, const nn::hid::CTR::TouchPanelStatus &previous)
109 {
110     if (previous.touch)
111     {
112         s32 diffX = now.x - previous.x;
113         s32 diffY = now.y - previous.y;
114         s32 absDiffX = nn::math::Abs(diffX);
115         s32 absDiffY = nn::math::Abs(diffY);
116 
117         if (diffX == 0 && diffY == 0)
118         {
119             DrawPoint(now.x, now.y);
120         }
121         else if (absDiffX < absDiffY)
122         {
123             for (int i = 0; i < absDiffY; ++i)
124             {
125                 DrawPoint(previous.x + i * diffX / absDiffY, previous.y + i * diffY / absDiffY);
126             }
127         }
128         else
129         {
130             for (int i = 0; i < absDiffX; ++i)
131             {
132                 DrawPoint(previous.x + i * diffX / absDiffX, previous.y + i * diffY / absDiffX);
133             }
134         }
135     }
136 }
137 
UpdateTexture(demo::RenderSystemDrawing & renderSystem,const bit8 * src)138 void UpdateTexture(demo::RenderSystemDrawing &renderSystem, const bit8 *src)
139 {
140     const int PIXEL_NUM_PER_BLOCK = TEXTURE_BLOCK_SIZE * TEXTURE_BLOCK_SIZE;
141     const int HORIZONTAL_BLOCK_NUM = MEMO_WIDTH  / TEXTURE_BLOCK_SIZE;
142     const int VERTICAL_BLOCK_NUM   = MEMO_HEIGHT / TEXTURE_BLOCK_SIZE;
143 
144     u8* textureDataBuffer = new bit8[4 * MEMO_TEXTURE_WIDTH * MEMO_TEXTURE_HEIGHT];
145     bit8 data;
146     for (int j = 0; j < VERTICAL_BLOCK_NUM; ++j)
147     {
148         u32* a = (u32*)(textureDataBuffer + (j + (MEMO_TEXTURE_HEIGHT - MEMO_HEIGHT) / TEXTURE_BLOCK_SIZE) * 4 * MEMO_TEXTURE_WIDTH / TEXTURE_BLOCK_SIZE * PIXEL_NUM_PER_BLOCK);
149         for (int i = 0; i < HORIZONTAL_BLOCK_NUM; ++i)
150         {
151             for (int k = 0; k < PIXEL_NUM_PER_BLOCK; ++k)
152             {
153                 if (k % 8 == 0)
154                 {
155                     data = *src;
156                     src++;
157                 }
158                 *a = (data & 1) ? 0 : 0xFFFFFFFF;
159                 a++;
160                 data >>= 1;
161             }
162         }
163     }
164 
165     if (s_TextureId != 0)
166     {
167         NN_PANIC_IF_FALSE(renderSystem.DeleteTexture(s_TextureId));
168     }
169 
170     GLenum format = GL_RGBA_NATIVE_DMP;
171     GLenum target = GL_TEXTURE_2D;
172     GLenum internalFormat = GL_RGBA_NATIVE_DMP;
173     GLenum type = GL_UNSIGNED_BYTE;
174     GLuint textureId = 0;
175 
176     renderSystem.GenerateTexture(target,
177         internalFormat, MEMO_TEXTURE_WIDTH, MEMO_TEXTURE_HEIGHT,
178         format, type, textureDataBuffer, textureId);
179 
180     delete[] textureDataBuffer;
181 
182     if ( textureId != 0 )
183     {
184         s_TextureId = textureId;
185     }
186 }
187 
DrawMemo(demo::RenderSystemDrawing & renderSystem,const bit8 * src)188 void DrawMemo(demo::RenderSystemDrawing &renderSystem, const bit8 *src)
189 {
190     UpdateTexture(renderSystem, src);
191 
192     renderSystem.FillTexturedRectangle(s_TextureId,
193         MEMO_X, MEMO_Y,
194         MEMO_WIDTH, MEMO_HEIGHT,
195         MEMO_WIDTH, MEMO_HEIGHT,
196         512, 256);
197 }
198 
ProceedDisplay0(demo::RenderSystemDrawing & renderSystem)199 void ProceedDisplay0(demo::RenderSystemDrawing &renderSystem)
200 {
201     renderSystem.SetRenderTarget(NN_GX_DISPLAY0);
202     renderSystem.SetColor(1.0f, 1.0f, 1.0f);
203     renderSystem.Clear();
204 
205     switch (s_RecState)
206     {
207     case REC_STATE_RUNNING:
208         renderSystem.DrawText(0, LINE_HEIGHT *  1, "Recording to %d", s_LoadFileNum);
209         break;
210     case REC_STATE_SUCCEEDED:
211         renderSystem.DrawText(0, LINE_HEIGHT *  1, "Recording Succeeded");
212         break;
213     case REC_STATE_FAILED:
214         renderSystem.DrawText(0, LINE_HEIGHT *  1, "Recording Failed");
215         break;
216     default:
217         break;
218     }
219 
220     switch(s_PlayState)
221     {
222     case PLAY_STATE_RUNNING:
223         renderSystem.DrawText(0, LINE_HEIGHT *  2, "Playing %d", s_LoadFileIndex);
224         break;
225     case PLAY_STATE_SUCCEEDED:
226         renderSystem.DrawText(0, LINE_HEIGHT *  2, "Playing Succeeded");
227         break;
228     case PLAY_STATE_STOPPED:
229         renderSystem.DrawText(0, LINE_HEIGHT *  2, "Playing Stopped");
230         break;
231     case PLAY_STATE_FAILED:
232         renderSystem.DrawText(0, LINE_HEIGHT *  2, "Playing Failed");
233         break;
234     default:
235         break;
236     }
237 
238     switch (s_BgState)
239     {
240     case STATE_BG_SAVE:
241         renderSystem.DrawText(0, LINE_HEIGHT *  5, "Saving...");
242         break;
243     case STATE_BG_SAVE_SUCCEEDED:
244         renderSystem.DrawText(0, LINE_HEIGHT *  5, "Saving Succeeded.");
245         break;
246     case STATE_BG_SAVE_FAILED:
247         renderSystem.DrawText(0, LINE_HEIGHT *  5, "Saving Failed.");
248         break;
249     case STATE_BG_LOAD:
250         renderSystem.DrawText(0, LINE_HEIGHT *  5, "Loading...");
251         break;
252     case STATE_BG_LOAD_SUCCEEDED:
253         renderSystem.DrawText(0, LINE_HEIGHT *  5, "Loading Succeeded.");
254         break;
255     case STATE_BG_LOAD_FAILED:
256         renderSystem.DrawText(0, LINE_HEIGHT *  5, "Loading Failed.");
257         break;
258     default:
259         break;
260     }
261 
262     renderSystem.DrawText(0, LINE_HEIGHT *  11, "A: %s Movie %d", (s_RecState == REC_STATE_RUNNING) ? "Stop" : "Record", s_LoadFileNum);
263     if (s_LoadFileNum > 0)
264     {
265         renderSystem.DrawText(0, LINE_HEIGHT *  12, "B: %s Movie %d", (s_PlayState == PLAY_STATE_RUNNING) ? "Stop" : "Play", s_LoadFileIndex);
266         renderSystem.DrawText(0, LINE_HEIGHT *  13, "Up & Down: Change movie No.");
267     }
268 
269     renderSystem.DrawText(0, LINE_HEIGHT *  15, "L: Clear buffer");
270 
271     renderSystem.DrawText(0, LINE_HEIGHT *  18, "data: %08X", s_BgSaveData);
272 
273     renderSystem.DrawText(0, LINE_HEIGHT *  20, "X: Save data in background");
274     renderSystem.DrawText(0, LINE_HEIGHT *  21, "Y: Load data in background");
275     renderSystem.DrawText(0, LINE_HEIGHT *  22, "Left & Right: Change data");
276 
277 
278     renderSystem.DrawText(100, LINE_HEIGHT *  25, "Recorded frame:%8d", s_RecordedFrameNum);
279     renderSystem.DrawText(100, LINE_HEIGHT *  26, "Dropped frame: %8d", s_DroppedFrameNum);
280 
281 
282     renderSystem.SwapBuffers();
283 
284 }
285 
ProceedDisplay1(demo::RenderSystemDrawing & renderSystem)286 void ProceedDisplay1(demo::RenderSystemDrawing &renderSystem)
287 {
288     renderSystem.SetRenderTarget(NN_GX_DISPLAY1);
289     renderSystem.Clear();
290     DrawMemo(renderSystem, s_MemoBuffer);
291 
292     renderSystem.SwapBuffers();
293 }
294 
ProceedDisplay(demo::RenderSystemDrawing & renderSystem)295 void ProceedDisplay(demo::RenderSystemDrawing &renderSystem)
296 {
297     ProceedDisplay0(renderSystem);
298     ProceedDisplay1(renderSystem);
299 }
300 
CallStartRecording()301 void CallStartRecording()
302 {
303     wchar_t fileName[256];
304     nstd::TSNPrintf(fileName, sizeof(fileName) / sizeof(fileName[0]), L"%ls%04d.dat",MEMO_FILE_NAME, s_LoadFileNum);
305     Result result = StartRecording(fileName);
306     if (result.IsSuccess())
307     {
308         s_RecordedFrameNum = 0;
309         s_DroppedFrameNum = 0;
310         s_RecState = REC_STATE_RUNNING;
311     }
312     else
313     {
314         NN_DBG_PRINT_RESULT(result);
315         s_RecState = REC_STATE_FAILED;
316     }
317 }
318 
CallStopRecording()319 void CallStopRecording()
320 {
321     Result result = StopRecording();
322     if (result.IsSuccess())
323     {
324         s_LoadFileNum++;
325         s_RecState = REC_STATE_SUCCEEDED;
326     }
327     else
328     {
329         NN_DBG_PRINT_RESULT(result);
330         s_RecState = REC_STATE_FAILED;
331     }
332 }
333 
CallStartPlaying()334 void CallStartPlaying()
335 {
336     wchar_t fileName[256];
337     nstd::TSNPrintf(fileName, sizeof(fileName) / sizeof(fileName[0]), L"%ls%04d.dat",MEMO_FILE_NAME, s_LoadFileIndex);
338     Result result = StartPlaying(fileName);
339     if (result.IsSuccess())
340     {
341         s_PlayState = PLAY_STATE_RUNNING;
342     }
343     else
344     {
345         NN_DBG_PRINT_RESULT(result);
346         s_PlayState = PLAY_STATE_FAILED;
347     }
348 }
349 
CallStopPlaying()350 void CallStopPlaying()
351 {
352     Result result = StopPlaying();
353     if (result.IsSuccess())
354     {
355         s_PlayState = PLAY_STATE_STOPPED;
356     }
357     else
358     {
359         NN_DBG_PRINT_RESULT(result);
360         s_PlayState = PLAY_STATE_FAILED;
361     }
362 }
363 
ProceedMainTask()364 void ProceedMainTask()
365 {
366     if (s_PlayState == PLAY_STATE_RUNNING)
367     {
368         PacketData *packet;
369         if (!LoadMemo(&packet))
370         {
371             // Does nothing when there is no loaded data.
372         }
373         else if (packet)
374         {
375             std::memcpy(s_MemoBuffer, packet->GetData(), packet->GetSize());
376             packet->Free();
377         }
378         else
379         {
380             NN_ERR_THROW_FATAL_ALL(FinishPlaying());
381             s_PlayState = PLAY_STATE_SUCCEEDED;
382         }
383     }
384 
385     if (s_TouchPanelStatus.touch)
386     {
387         DrawLine(s_TouchPanelStatus, s_PreviousTouchPanel);
388     }
389 
390     if (s_RecState == REC_STATE_RUNNING)
391     {
392         ENUM_RECORDING_RESULT result = SaveMemo(s_MemoBuffer);
393         if (result == RECORDING_RESULT_BUFFER_FULL)
394         {
395             s_DroppedFrameNum++;
396         }
397         else if (result == RECORDING_RESULT_FILE_FULL)
398         {
399             CallStopRecording();
400         }
401         else
402         {
403             s_RecordedFrameNum++;
404         }
405     }
406 }
407 
WorkerThread()408 void WorkerThread()
409 {
410     while (s_IsWorkerThreadAlive)
411     {
412         s_WorkerEvent.Wait();
413         switch (s_BgTask)
414         {
415         case BG_TASK_SAVE:
416             s_BgState = STATE_BG_SAVE;
417             s_BgState = (BgSave(s_BgSaveData).IsSuccess()) ? STATE_BG_SAVE_SUCCEEDED : STATE_BG_SAVE_FAILED;
418             break;
419         case BG_TASK_LOAD:
420             s_BgState = STATE_BG_LOAD;
421             s_BgState = (BgLoad(s_BgSaveData).IsSuccess()) ? STATE_BG_LOAD_SUCCEEDED : STATE_BG_LOAD_FAILED;
422             break;
423         default:
424             s_BgState = STATE_BG_NONE;
425             break;
426         }
427 
428         s_BgTask = BG_TASK_NONE;
429     }
430 }
431 
ProceedHid()432 void ProceedHid()
433 {
434     static nn::hid::CTR::PadReader padReader;
435     static nn::hid::CTR::PadStatus padStatus;
436     static nn::hid::CTR::TouchPanelReader touchPanelReader;
437 
438     if (!padReader.ReadLatest(&padStatus))
439     {
440         nn::dbg::detail::TPrintf("Getting pad status failed.\n");
441     }
442 
443     if (padStatus.trigger & nn::hid::BUTTON_A)
444     {
445         if (s_RecState != REC_STATE_RUNNING)
446         {
447             CallStartRecording();
448         }
449         else if (s_RecState == REC_STATE_RUNNING)
450         {
451             CallStopRecording();
452         }
453     }
454     else if (padStatus.trigger & nn::hid::BUTTON_B)
455     {
456         if (s_PlayState != PLAY_STATE_RUNNING)
457         {
458             CallStartPlaying();
459         }
460         else if (s_PlayState == PLAY_STATE_RUNNING)
461         {
462             CallStopPlaying();
463         }
464     }
465     else if ((padStatus.trigger & nn::hid::BUTTON_X) && s_BgTask == BG_TASK_NONE)
466     {
467         s_BgTask = BG_TASK_SAVE;
468         s_WorkerEvent.Signal();
469     }
470     else if ((padStatus.trigger & nn::hid::BUTTON_Y) && s_BgTask == BG_TASK_NONE)
471     {
472         s_BgTask = BG_TASK_LOAD;
473         s_WorkerEvent.Signal();
474     }
475     else if (padStatus.trigger & nn::hid::BUTTON_L)
476     {
477         std::memset(s_MemoBuffer, 0, MEMO_BUFFER_SIZE);
478     }
479     else if (padStatus.trigger & nn::hid::BUTTON_LEFT)
480     {
481         s_BgSaveData--;
482     }
483     else if (padStatus.trigger & nn::hid::BUTTON_RIGHT)
484     {
485         s_BgSaveData++;
486     }
487     else if (padStatus.trigger & nn::hid::BUTTON_UP && s_LoadFileNum > 0 && s_PlayState != PLAY_STATE_RUNNING)
488     {
489         s_LoadFileIndex = (s_LoadFileIndex + 1) % s_LoadFileNum;
490     }
491     else if (padStatus.trigger & nn::hid::BUTTON_DOWN && s_LoadFileNum > 0 && s_PlayState != PLAY_STATE_RUNNING)
492     {
493         s_LoadFileIndex = (s_LoadFileIndex - 1 + s_LoadFileNum) % s_LoadFileNum;
494     }
495 
496     s_PreviousTouchPanel = s_TouchPanelStatus;
497     if (!touchPanelReader.ReadLatest(&s_TouchPanelStatus))
498     {
499         nn::dbg::detail::TPrintf("Getting touch panel status failed.\n");
500     }
501 }
502 
503 // Initializes the save data.
FormatSaveData()504 void FormatSaveData()
505 {
506     const size_t    maxFiles        = 8;
507     const size_t    maxDirectories  = 8;
508     const bool      isDuplicateAll  = true;
509 
510     // Format the save data region
511     Result result = nn::fs::FormatSaveData(maxFiles, maxDirectories, isDuplicateAll);
512     if (result.IsFailure())
513     {
514         NN_LOG("FormatSaveData() failed.\n");
515         NN_ERR_THROW_FATAL_ALL(result);
516     }
517 
518     // Mount in order to compose the data default state
519     result = nn::fs::MountSaveData(SAVE_DATA_ARCHIVE);
520     if (result.IsFailure())
521     {
522         NN_LOG("MountSaveData() failed : Cannot use save data!\n");
523         NN_ERR_THROW_FATAL_ALL(result);
524     }
525 
526     // Create each file
527     nn::fs::CreateFile(DATA_FILE_NAME, sizeof(SaveData));
528 
529     // Commit
530     NN_ERR_THROW_FATAL_ALL(nn::fs::CommitSaveData(SAVE_DATA_ARCHIVE));
531 
532     // Unmount
533     nn::fs::Unmount(SAVE_DATA_ARCHIVE);
534 }
535 
536 // Save data check when starting the application
InitializeSaveData()537 void InitializeSaveData()
538 {
539     Result result = nn::fs::MountSaveData(SAVE_DATA_ARCHIVE);
540     if (result.IsFailure())
541     {
542         NN_DBG_PRINT_RESULT(result);
543 
544         if ((result <= nn::fs::ResultNotFormatted()) ||
545             (result <= nn::fs::ResultBadFormat()) ||
546             (result <= nn::fs::ResultVerificationFailed()))
547         {
548             // Save data needs to be initialized
549             FormatSaveData();
550         }
551         else
552         {
553             // Unexpected errors
554             NN_ERR_THROW_FATAL_ALL(result);
555         }
556     }
557     else
558     {
559         nn::fs::Unmount(SAVE_DATA_ARCHIVE);
560     }
561 }
562 
563 // Expanded save data check when starting the application
InitializeExtSaveData()564 void InitializeExtSaveData()
565 {
566     Result result = nn::fs::MountExtSaveData(EXT_ARCHIVE, EXT_SAVEDATA_ID);
567     if (result.IsFailure())
568     {
569         NN_DBG_PRINT_RESULT(result);
570 
571         if ((result <= nn::fs::ResultNotFound()) ||
572             (result <= nn::fs::ResultNotFormatted()) ||
573             (result <= nn::fs::ResultBadFormat()) ||
574             (result <= nn::fs::ResultVerificationFailed()))
575         {
576             nn::fs::DeleteExtSaveData(EXT_SAVEDATA_ID);
577             bit8 iconData[64];
578             NN_ERR_THROW_FATAL_ALL(nn::fs::CreateExtSaveData(EXT_SAVEDATA_ID, iconData, sizeof(iconData), 0, MEMO_FILE_NUM_MAX));
579             NN_ERR_THROW_FATAL_ALL(nn::fs::MountExtSaveData(EXT_ARCHIVE, EXT_SAVEDATA_ID));
580         }
581         else
582         {
583             // Unexpected errors
584             NN_ERR_THROW_FATAL_ALL(result);
585         }
586     }
587 }
588 
589 } // namespace streaming
590 
591 using namespace streaming;
592 
MainLoop(demo::RenderSystemDrawing & renderSystem)593 void MainLoop(demo::RenderSystemDrawing &renderSystem)
594 {
595     ProceedHid();
596     ProceedMainTask();
597     ProceedDisplay(renderSystem);
598 
599     nngxWaitVSync(NN_GX_DISPLAY_BOTH);
600 }
601 
Initialize(demo::RenderSystemDrawing & renderSystem)602 void Initialize(demo::RenderSystemDrawing &renderSystem)
603 {
604     nn::fs::Initialize();
605     InitializeSaveData();
606     InitializeExtSaveData();
607 
608     nn::fs::SetAnalysisLog(true);
609     renderSystem.SetClearColor(NN_GX_DISPLAY1, 0.5f, 0.5f, 0.5f, 0.0f);
610 
611     s_LoadFileNum = 0;
612     s_LoadFileIndex = 0;
613     while (true)
614     {
615         wchar_t fileName[256];
616         nstd::TSNPrintf(fileName, sizeof(fileName) / sizeof(fileName[0]), L"%ls%04d.dat",MEMO_FILE_NAME, s_LoadFileNum);
617 
618         nn::fs::FileInputStream file;
619         Result result = file.TryInitialize(fileName);
620         if (result.IsFailure())
621         {
622             break;
623         }
624         s_LoadFileNum++;
625     }
626 
627     s_WorkerEvent.Initialize(false);
628     s_IsWorkerThreadAlive = true;
629     NN_ERR_THROW_FATAL_ALL(s_WorkerThread.TryStartUsingAutoStack(&WorkerThread, 4 * 1024, nn::os::Thread::GetCurrentPriority() + 1));
630 }
631 
Finalize()632 void Finalize()
633 {
634     s_IsWorkerThreadAlive = false;
635     s_WorkerEvent.Signal();
636     s_WorkerThread.Join();
637 }
638 
639 }} // namespace sample::fs
640