int loom_net_initialize() { // Platform-specific init. #if LOOM_PLATFORM == LOOM_PLATFORM_WIN32 WSADATA wsaData; WORD wVersionRequested = MAKEWORD(2, 0); int err = WSAStartup(wVersionRequested, &wsaData); if (err != 0) { lmLogError(netLogGroup, "Failed WinSock initalization with error %d", err); return 0; } if (((LOBYTE(wsaData.wVersion) != 2) || (HIBYTE(wsaData.wVersion) != 0)) && ((LOBYTE(wsaData.wVersion) != 1) || (HIBYTE(wsaData.wVersion) != 1))) { lmLogError(netLogGroup, "Failed WinSock initalization due to version mismatch"); WSACleanup(); return 0; } // Sanity checks. lmAssert(sizeof(SOCKET) <= sizeof(loom_socketId_t), "Can't pack a SOCKET into loom_socketId_t"); lmLogDebug(netLogGroup, "Initialized WinSock"); return 1; #else // Ignore sigpipe. lmLogDebug(netLogGroup, "Disabling signal SIGPIPE"); signal(SIGPIPE, SIG_IGN); return 1; #endif }
NativeDelegateCallNote *NativeDelegate::prepCallbackNote() const { lmLogDebug(gNativeDelegateGroup, "Considering async callback %x", this); // Are noting currently? Just work with that. if(_activeNote) { lmLogDebug(gNativeDelegateGroup, " OUT due to activeNote already present"); return _activeNote; } // See if we should try to go async. if(_allowAsync == false) { lmLogDebug(gNativeDelegateGroup, " OUT due to async being disallowed"); return NULL; } if(smMainThreadID == platform_getCurrentThreadId()) return NULL; // Only do this for async delegates off main thread. lmLogDebug(gNativeDelegateGroup, "Prepping async callback!"); _activeNote = lmNew(NULL) NativeDelegateCallNote(this); return _activeNote; }
void *loom_asset_lock(const char *name, unsigned int type, int block) { const char *namePtr = stringtable_insert(name); loom_mutex_lock(gAssetLock); // Look it up. loom_asset_t *asset = loom_asset_getAssetByName(namePtr, 1); lmAssert(asset != NULL, "Didn't get asset even though we should have!"); // If not loaded, and we aren't ready to block, return NULL. if ((block == 0) && (asset->state != loom_asset_t::Loaded)) { lmLogDebug(gAssetLogGroup, "Not blocking and not loaded yet; lock of '%s' failed.", namePtr); loom_mutex_unlock(gAssetLock); return NULL; } // Otherwise, let's force it to load now. if (asset->state != loom_asset_t::Loaded) { lmLogDebug(gAssetLogGroup, "Blocking so forcing load of '%s'.", namePtr); loom_asset_preload(namePtr); lmAssert(loom_asset_isOnTrackToLoad(asset), "Preloaded but wasn't on track to load!"); while (loom_asset_checkLoadedPercentage(namePtr) != 1.f && loom_asset_isOnTrackToLoad(asset)) { lmLogDebug(gAssetLogGroup, "Pumping load of '%s'...", namePtr); loom_asset_pump(); } if (asset->state != loom_asset_t::Loaded) { lmLogError(gAssetLogGroup, "Unable to load asset '%s'!", name); loom_mutex_unlock(gAssetLock); return NULL; } } // Check type. if (asset->type != type) { lmLogError(gAssetLogGroup, "Tried to lock asset '%s' with wrong type, assetType=%x, requestedType=%x", name, asset->type, type); loom_mutex_unlock(gAssetLock); return NULL; } // Inc count. asset->blob->incRef(); loom_mutex_unlock(gAssetLock); lmLogDebug(gAssetLogGroup, "Locked asset '%s'...", namePtr); // Return ptr. return asset->blob->bits; }
void Graphics::reset(int width, int height, uint32_t flags) { lmAssert(sInitialized, "Please make sure to call Graphics::initialize first"); lmLogDebug(gGFXLogGroup, "Graphics::reset - %dx%d %x", width, height, flags); // clear context loss state sContextLost = false; Loom2D::Matrix mvp; mvp.scale(2.0f / width, 2.0f / height); mvp.translate(-1.0f, -1.0f); //mvp.copyToMatrix4(sMVPInverted); // Inverted is normal due to OpenGL origin being bottom left if (!(flags & FLAG_INVERTED)) { mvp.scale(1.0f, -1.0f); } mvp.copyToMatrix4(sMVP); sCurrentModelViewProjection = sMVP; // cache current values sWidth = width; sHeight = height; sFlags = flags; }
// We don't track every file. This function filters potential files. static bool checkInWhitelist(utString path) { // Note the platform-specific version of our whitelisted folders. static utString assetPath = "./assets"; platform_normalizePath(const_cast<char*>(assetPath.c_str())); static utString binPath = "./bin"; platform_normalizePath(const_cast<char*>(binPath.c_str())); static utString srcPath = "./src"; platform_normalizePath(const_cast<char*>(srcPath.c_str())); // Just prefix match against assets for now - ignore things in other folders. lmLogDebug(gAssetAgentLogGroup, "Whitelisting path %s prefix %s\n", path.c_str(), path.substr(0, 6).c_str()); platform_normalizePath(const_cast<char*>(path.c_str())); if (path.substr(path.length() - 3, 3) == "tmp") { return false; } if (path.substr(0, 8) == assetPath) { return true; } if (path.substr(0, 5) == srcPath) { return true; } if (path.substr(0, 5) == binPath) { return true; } return false; }
void loom_asset_flush(const char *name) { // Currently we only want to do this on the main thread so piggy back on the // native delegate sanity check to bail if on secondary thread. if(platform_getCurrentThreadId() != LS::NativeDelegate::smMainThreadID && LS::NativeDelegate::smMainThreadID != 0xBAADF00D) return; loom_mutex_lock(gAssetLock); // Delete it + unload it. loom_asset_t *asset = loom_asset_getAssetByName(name, 0); if(!asset || asset->isSupplied) { loom_mutex_unlock(gAssetLock); return; } lmLogDebug(gAssetLogGroup, "Flushing '%s'", name); if (asset->blob) { asset->blob->decRef(); asset->blob = NULL; } asset->state = loom_asset_t::Unloaded; // Fire subscribers. if(!gShuttingDown) loom_asset_notifySubscribers(asset->name.c_str()); loom_mutex_unlock(gAssetLock); }
void *loom_asset_imageDeserializer( void *buffer, size_t bufferLen, LoomAssetCleanupCallback *dtor ) { loom_asset_image_t *img; lmAssert(buffer != NULL, "buffer should not be null"); img = (loom_asset_image_t*)lmAlloc(gAssetAllocator, sizeof(loom_asset_image_t)); // parse any orientation info from exif format img->orientation = exifinfo_parse_orientation(buffer, (unsigned int)bufferLen); img->bits = stbi_load_from_memory((const stbi_uc *)buffer, (int)bufferLen, &img->width, &img->height, &img->bpp, 4); *dtor = loom_asset_imageDtor; if(!img->bits) { lmLogError(gImageAssetGroup, "Image load failed due to this cryptic reason: %s", stbi_failure_reason()); lmFree(gAssetAllocator, img); return 0; } lmLogDebug(gImageAssetGroup, "Allocated %d bytes for an image!", img->width * img->height * 4); return img; }
void finishProfilerBlock(profilerBlock_t *block) { int curTime = platform_getMilliseconds(); if (curTime - block->startTime > block->thresholdMs) { lmLogDebug(gProfilerLogGroup, "%s exceeded threshold (%dms > %dms)", block->name, curTime - block->startTime, block->thresholdMs); } }
// Helper to fully shut down the listen socket; helps quite a bit on OS X. static void shutdownListenSocket() { if (gListenSocket) { lmLogDebug(gAssetAgentLogGroup, "Shutting down listen socket..."); loom_net_closeTCPSocket(gListenSocket); lmLog(gAssetAgentLogGroup, "Done! Goodbye."); gListenSocket = 0; } }
static void loom_net_setSocketReuseAddress(loom_socketId_t s, int reuse) { #if LOOM_PLATFORM_IS_APPLE == 1 int reuseTmp = reuse; int status = setsockopt((SOCKET)s, SOL_SOCKET, SO_REUSEPORT, &reuseTmp, sizeof(reuseTmp)); lmAssert(status == 0, "Failed trying to set socket reuse address status due to %d", status); #else // If you get issues where the socket stays bound after a listening process (like the assetAgent) // terminates you may need to set SO_REUSEADDR or SO_REUSEPORT or equivalent on your platform. lmLogDebug(netLogGroup, "Note: this platform doesn't support reusing port, but it probably doesn't matter."); #endif }
int loom_net_writeTCPSocket(loom_socketId_t s, void *buffer, int bytesToWrite) { #if LOOM_PLATFORM == LOOM_PLATFORM_WIN32 int winsockErrorCode; #endif int bytesLeft = bytesToWrite; for ( ; ; ) { int result = send((SOCKET)s, buffer, bytesLeft, MSG_NOSIGNAL); if (result >= 0) { bytesLeft -= result; if (bytesLeft != 0) { lmLogDebug(netLogGroup, "Partial write on socket %d, expected %d but wrote %d! Retrying...", s, bytesToWrite, result); // Set up to try again by advancing into the buffer. buffer = (void *)((char *)buffer + result); continue; } return bytesToWrite - bytesLeft; } #if LOOM_PLATFORM == LOOM_PLATFORM_WIN32 winsockErrorCode = WSAGetLastError(); if (winsockErrorCode != WSAEWOULDBLOCK) { break; } #else if (errno != EAGAIN) { break; } #endif if (loom_net_isSocketDead(s)) { break; } loom_thread_sleep(5); } return -1; }
// Convert a path into its canonical form. static int makeAssetPathCanonical(const char *pathIn, char pathOut[MAXPATHLEN]) { // First thing, safe pathOut. pathOut[0] = 0; char cwd[MAXPATHLEN]; char* cwdres = getcwd(cwd, MAXPATHLEN); char* resolvedPathPtr = NULL; // Note, man page suggests that realpath won't work right for // non-existant folders/files. if (cwdres != NULL) { resolvedPathPtr = platform_realpath(pathIn, NULL); } if (resolvedPathPtr == NULL) { lmLogError(gAssetAgentLogGroup, "Failed to resolve path %s via realpath due to %s", pathIn, strerror(errno)); return 0; } // Now slurp off the prefix if possible. lmLogDebug(gAssetAgentLogGroup, "Checking for prefix '%s' '%s' '%s'", resolvedPathPtr, cwd, pathIn); lmAssert(resolvedPathPtr, "No resolved path!"); lmAssert(cwd, "Could not get working dir?"); const char *prefixOffset = strstr(resolvedPathPtr, cwd); if (prefixOffset != NULL) { // Great, it's known to us. strncpy(pathOut, prefixOffset + strlen(cwd) + 1, MAXPATHLEN); // free(resolvedPathPtr); return 1; } else { // Nope, unknown. // free(resolvedPathPtr); return 0; } }
int main(int argc, char *argv[]) { #ifdef MSVC_DEBUG_HEAP // Get current flag int tmpFlag = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG); tmpFlag |= _CRTDBG_ALLOC_MEM_DF; tmpFlag |= _CRTDBG_LEAK_CHECK_DF; // Set flag to the new value. _CrtSetDbgFlag(tmpFlag); #endif #ifdef WIN32 // When on windows, do some workarounds so our console window // behaves properly. // put the program name into argv[0] char filename[_MAX_PATH]; GetModuleFileNameA(NULL, filename, _MAX_PATH); argv[0] = filename; bool fromRuby = false; for (int i = 1; i < argc; i++) { if (!stricmp(argv[i], "ProcessID") && i + 1 < argc) { fromRuby = true; char *pEnd; long int pid = strtol (argv[i + 1], &pEnd, 10); LS::Process::rubyProcessId = pid; memmove(argv + i, argv + i + 2, (argc - i - 2)*sizeof(char*)); argc -= 2; i--; break; } } LS::Process::consoleAttached = false; if (!fromRuby && AttachConsole(ATTACH_PARENT_PROCESS)) { HANDLE consoleHandleOut = GetStdHandle(STD_OUTPUT_HANDLE); int fdOut = _open_osfhandle((intptr_t)consoleHandleOut, _O_TEXT); FILE *fpOut = _fdopen(fdOut, "w"); *stdout = *fpOut; setvbuf(stdout, NULL, _IONBF, 0); //redirect unbuffered STDERR to the console HANDLE consoleHandleError = GetStdHandle(STD_ERROR_HANDLE); int fdError = _open_osfhandle((intptr_t)consoleHandleError, _O_TEXT); FILE *fpError = _fdopen(fdError, "w"); *stderr = *fpError; setvbuf(stderr, NULL, _IONBF, 0); LS::Process::consoleAttached = true; } #endif // Initialize logging. loom_log_initialize(); LSLuaState::initCommandLine(argc, (const char**) argv); /* Enable standard application logging */ SDL_LogSetAllPriority(SDL_LOG_PRIORITY_INFO); SDL_LogSetOutputFunction(sdlLogOutput, NULL); SDL_Init( SDL_INIT_TIMER | SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_HAPTIC | SDL_INIT_GAMECONTROLLER | SDL_INIT_EVENTS ); int ret; #if LOOM_RENDERER_OPENGLES2 ret = SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES); lmAssert(ret == 0, "SDL Error: %s", SDL_GetError()); ret = SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2); lmAssert(ret == 0, "SDL Error: %s", SDL_GetError()); #endif int stencilSize = 1; ret = SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, stencilSize); lmAssert(ret == 0, "SDL Error: %s", SDL_GetError()); // Set up SDL window. if ((gSDLWindow = SDL_CreateWindow( "Loom", 0, 0, 100, 100, SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIDDEN #if LOOM_PLATFORM == LOOM_PLATFORM_IOS | SDL_WINDOW_BORDERLESS #endif | SDL_WINDOW_OPENGL | SDL_WINDOW_ALLOW_HIGHDPI)) == NULL) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_CreateWindow(): %s\n", SDL_GetError()); exit(0); } gContext = SDL_GL_CreateContext(gSDLWindow); if (!gContext) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_GL_CreateContext(): %s\n", SDL_GetError()); exit(2); } ret = SDL_GL_SetSwapInterval(-1); if (ret != 0) { lmLog(coreLogGroup, "Late swap tearing not supported, using vsync"); SDL_GL_SetSwapInterval(1); } // And show the window with proper settings. SDL_SetWindowPosition(gSDLWindow, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED); SDL_StopTextInput(); // Initialize Loom! loom_appSetup(); supplyEmbeddedAssets(); /* Main render loop */ gLoomExecutionDone = 0; /* Display SDL version */ SDL_version compiled; SDL_version linked; SDL_VERSION(&compiled); SDL_GetVersion(&linked); lmLogDebug(coreLogGroup, "Compiled with SDL version %d.%d.%d and linking against SDL version %d.%d.%d ...", compiled.major, compiled.minor, compiled.patch, linked.major, linked.minor, linked.patch); /* Game Controller */ // Enable controller events SDL_GameControllerEventState(SDL_ENABLE); //Open all connected game controllers LoomGameController::openAll(); #ifdef __EMSCRIPTEN__ emscripten_set_main_loop(loop, 0, 1); #else while (!gLoomExecutionDone) loop(); #endif //Close all opened game controllers before closing application LoomGameController::closeAll(); loom_appShutdown(); #ifdef WIN32 LS::Process::cleanupConsole(); #endif exit(0); return 0; /* to prevent compiler warning */ }
void NativeDelegate::disallowAsync() { lmLogDebug(gNativeDelegateGroup, "SETTING ASYNC OFF %x", this); _allowAsync = false; }
virtual bool handleMessage(int fourcc, AssetProtocolHandler *handler, NetworkBuffer& buffer) { switch (fourcc) { case LOOM_FOURCC('F', 'I', 'L', 'E'): { // How many pending files? gPendingFiles = buffer.readInt(); loom_asset_notifyPendingCountChange(); // Read the filename. char *path; int fileStringLength; buffer.readString(&path, &fileStringLength); // And the file length. int bitsLength = buffer.readInt(); // Checkpoint at end! buffer.readCheckpoint(0xDEADBEE3); // Prepare the buffer! if (pendingFile != NULL) { lmLogError(gAssetLogGroup, "Got a new FILE '%s' while still processing existing file '%s'!", path, pendingFilePath.c_str()); wipePendingData(); } // Update the pending file state. pendingFilePath = path; lmFree(NULL, path); path = NULL; pendingFileLength = bitsLength; pendingFile = (const char *)lmAlloc(gAssetAllocator, pendingFileLength); // Log it. lmLogDebug(gAssetLogGroup, "FILE '%s' %d bytes incoming.", pendingFilePath.c_str(), bitsLength); // Awesome, sit back and wait for chunks to come in. return true; } case LOOM_FOURCC('F', 'C', 'H', 'K'): { // How many pending files? gPendingFiles = buffer.readInt(); loom_asset_notifyPendingCountChange(); // Get the offset. int chunkOffset = buffer.readInt(); // Read bits into the buffer. char *fileBits; int fileBitsLength; buffer.readString(&fileBits, &fileBitsLength); memcpy((void *)(pendingFile + chunkOffset), (void *)fileBits, fileBitsLength); lmFree(NULL, fileBits); // Checkpoint at end! buffer.readCheckpoint(0xDEADBEE2); int lastByteOffset = chunkOffset + fileBitsLength; // Log it. lmLogDebug(gAssetLogGroup, "FILE '%s' %d of %d bytes!", pendingFilePath.c_str(), lastByteOffset, pendingFileLength); // If it's the last one, instate it and wipe our buffer. if (lastByteOffset == pendingFileLength) { // And this resolves a file so decrement the pending count. This way // we will get to zero. gPendingFiles--; loom_asset_notifyPendingCountChange(); // Instate the new asset data. loom_asset_t *asset = loom_asset_getAssetByName(pendingFilePath.c_str(), 1); int assetType = loom_asset_recognizeAssetTypeFromPath(pendingFilePath); if (assetType == 0) { lmLogDebug(gAssetLogGroup, "Couldn't infer file type for '%s', ignoring.", pendingFilePath.c_str()); wipePendingData(); return true; } lmLogWarn(gAssetLogGroup, "Applying new version of '%s', %d bytes.", pendingFilePath.c_str(), pendingFileLength); LoomAssetCleanupCallback dtor = NULL; void *assetBits = loom_asset_deserializeAsset(pendingFilePath.c_str(), assetType, pendingFileLength, (void *)pendingFile, &dtor); asset->instate(assetType, assetBits, dtor); // And wipe the pending date. wipePendingData(); } } return true; } return false; }
// Take a difference report from compareFileEntries and issue appropriate // file modification notes, and check whether they have settled. If so, // transmit updates to clients. static void processFileEntryDeltas(utArray<FileEntryDelta> *deltas) { int curTime = platform_getMilliseconds(); loom_mutex_lock(gFileScannerLock); // Update the pending list with all the stuff we've seen. for (UTsize i = 0; i < deltas->size(); i++) { // Get the delta. const FileEntryDelta& fed = deltas->at(i); // If it's removal, we don't currently send a notification. if (fed.action == FileEntryDelta::Removed) { continue; } // If it's not whitelisted, ignore it. if (!checkInWhitelist(fed.path)) { continue; } // Note it in the pending modification list. bool sawInList = false; for (UTsize i = 0; i < gPendingModifications.size(); i++) { FileModificationNote& fmn = gPendingModifications.at(i); if (strcmp(fmn.path, fed.path.c_str())) { continue; } // Match - update time. lmLogDebug(gAssetAgentLogGroup, "FILE CHANGING - '%s'", fed.path.c_str()); fmn.lastSeenTime = curTime; sawInList = true; } if (!sawInList) { FileModificationNote fmn; fmn.path = stringtable_insert(fed.path.c_str()); fmn.lastSeenTime = curTime; gPendingModifications.push_back(fmn); lmLogDebug(gAssetAgentLogGroup, "FILE CHANGED - '%s'", fed.path.c_str()); } } // Now, walk the pending list and send everyone who hasn't been touched for the settling period. // See how many files we're sending and note that state. const int settleTimeMs = 750; int transferStartTime = platform_getMilliseconds(); int totalPendingTransfers = 0; for (UTsize i = 0; i < gPendingModifications.size(); i++) { // Only consider pending items that have aged out. FileModificationNote& fmn = gPendingModifications.at(i); if (curTime - fmn.lastSeenTime < settleTimeMs) { continue; } totalPendingTransfers++; } bool didWeNotifyUserAboutPending = false; for (UTsize i = 0; i < gPendingModifications.size(); i++) { // Only consider pending items that have aged out. FileModificationNote& fmn = gPendingModifications.at(i); if (curTime - fmn.lastSeenTime < settleTimeMs) { continue; } // Make the path canonical. utString filename = fmn.path; char canonicalFile[MAXPATHLEN]; makeAssetPathCanonical(filename.c_str(), canonicalFile); // Note: we don't deal with deleted files properly (by uploading new state) because realpath // only works right when the file exists. So we just skip doing anything about it. // Note we are using gActiveHandlers.size() outside of a lock, but this is ok as it's a word. if ((strstr(canonicalFile, ".loom") || strstr(canonicalFile, ".ls")) && (gActiveHandlers.size() > 0)) { lmLog(gAssetAgentLogGroup, "Changed '%s'", canonicalFile); } if (canonicalFile[0] == 0) { lmLog(gAssetAgentLogGroup, " o Ignoring file missing from the asset folder!"); // Remove from the pending list. gPendingModifications.erase(i); i--; continue; } // Queue the callback. enqueueFileChangeCallback(canonicalFile); // Map the file. void *fileBits = NULL; long fileBitsLength = 0; if (!platform_mapFile(canonicalFile, &fileBits, &fileBitsLength)) { lmLog(gAssetAgentLogGroup, " o Skipping due to file failing to map."); continue; } // Loop over the active sockets. loom_mutex_lock(gActiveSocketsMutex); // Blast it out to all clients. for (UTsize j = 0; j < gActiveHandlers.size(); j++) { // If it's for a specific client then only send to that client. if ((fmn.onlyForClient != -1) && (fmn.onlyForClient != gActiveHandlers[j]->getId())) { continue; } gActiveHandlers[j]->sendFile(canonicalFile, fileBits, fileBitsLength, totalPendingTransfers); // If it has been more than a second, note that we are still working. const int remainingTransferCount = (totalPendingTransfers * gActiveHandlers.size()) - j; if (((platform_getMilliseconds() - transferStartTime) > 2000) && (remainingTransferCount > 1)) { transferStartTime = platform_getMilliseconds(); lmLog(gAssetAgentLogGroup, "Still transferring files. %d to go!", remainingTransferCount - 1); didWeNotifyUserAboutPending = true; } } loom_mutex_unlock(gActiveSocketsMutex); totalPendingTransfers--; // Unmap the file. platform_unmapFile(fileBits); // Remove from the pending list. gPendingModifications.erase(i); i--; } loom_mutex_unlock(gFileScannerLock); if (didWeNotifyUserAboutPending) { lmLog(gAssetAgentLogGroup, "Done transferring files!"); } }
void *loom_asset_soundDeserializer( void *buffer, size_t bufferLen, LoomAssetCleanupCallback *dtor ) { loom_asset_sound_t *sound = (loom_asset_sound_t*)lmAlloc(gAssetAllocator, sizeof(loom_asset_sound_t)); memset(sound, 0, sizeof(loom_asset_sound_t)); unsigned char *charBuff = (unsigned char *)buffer; // Look for magic header in buffer. if(charBuff[0] == 0x4f && charBuff[1] == 0x67 && charBuff[2] == 0x67 && charBuff[3] == 0x53) { // It's an Ogg, assume vorbis and throw it to stb_vorbis. int channels = 0; short *outputBuffer = NULL; int sampleCount = stb_vorbis_decode_memory(charBuff, (int)bufferLen, &channels, &outputBuffer); if(sampleCount < 0) { lmLogError(gSoundAssetGroup, "Failed to decode Ogg Vorbis"); loom_asset_soundDtor(&sound); return NULL; } sound->channels = channels; sound->bytesPerSample = 2; sound->sampleCount = sampleCount; sound->bufferSize = sampleCount * channels * 2; sound->sampleRate = 44100; // TODO: This should be variable // We can skip this if we get clever about allocations in stbv. sound->buffer = lmAlloc(gAssetAllocator, sound->bufferSize); memcpy(sound->buffer, outputBuffer, sound->bufferSize); free(outputBuffer); } else if((charBuff[0] == 0x49 // ID3 && charBuff[1] == 0x44 && charBuff[2] == 0x33) || (charBuff[0] == 0xff // Missing ID3 Tag && charBuff[1] == 0xfb)) { // It's an MP3, y'all! short *outBuffer = (short*)lmAlloc(gAssetAllocator, MP3_MAX_SAMPLES_PER_FRAME * 2); mp3_info_t mp3Info; // Decode once to get total size. size_t totalBytes = 0; size_t bytesRead = 0, bytesLeft = bufferLen; mp3_decoder_t decmp3 = mp3_create(); for(;;) { int bytesDecoded = mp3_decode(decmp3, charBuff + bytesRead, (int)bytesLeft, outBuffer, &mp3Info); bytesRead += bytesDecoded; bytesLeft -= bytesDecoded; totalBytes += mp3Info.audio_bytes; if(bytesDecoded > 0) continue; // Clean up. mp3_done(decmp3); break; } // Great, set up the sound asset. // TODO: Warn about non 44.1khz mp3s. sound->channels = mp3Info.channels; sound->bytesPerSample = 2; sound->sampleCount = (int)totalBytes / sound->bytesPerSample; sound->bufferSize = sound->channels * sound->bytesPerSample * sound->sampleCount; sound->sampleRate = 44100; // TODO: This should be variable sound->buffer = lmAlloc(gAssetAllocator, sound->bufferSize); // Decode again to get real samples. decmp3 = mp3_create(); bytesRead = 0; bytesLeft = bufferLen; int curBufferOffset = 0; for(;;) { int bytesDecoded = mp3_decode(decmp3, charBuff + bytesRead, (int)bytesLeft, outBuffer, &mp3Info); bytesRead += bytesDecoded; bytesLeft -= bytesDecoded; memcpy(((unsigned char*)sound->buffer) + curBufferOffset, outBuffer, mp3Info.audio_bytes); curBufferOffset += mp3Info.audio_bytes; if(bytesDecoded > 0) continue; // Clean up. mp3_done(decmp3); break; } // Awesome, all set! lmFree(gAssetAllocator, outBuffer); } else if(charBuff[0] == 0x52 // 'RIFF' && charBuff[1] == 0x49 && charBuff[2] == 0x46 && charBuff[3] == 0x46) { // We've got a wav file wav_info wav; bool wavLoadSuccess = load_wav(charBuff, bufferLen, NULL, &wav); if (!wavLoadSuccess) { lmLogError(gSoundAssetGroup, "Failed to load wav format info"); loom_asset_soundDtor(sound); return 0; } sound->channels = wav.numChannels; sound->bytesPerSample = wav.sampleSize / 8; // wav sample size is in bits if (sound->bytesPerSample != 1 && sound->bytesPerSample != 2) { lmLogError(gSoundAssetGroup, "Unsupported wav format. Currently only 8-bit or 16-bit PCM are supported"); loom_asset_soundDtor(sound); return 0; } sound->bufferSize = wav.sampleDataSize; sound->sampleCount = sound->bufferSize / sound->bytesPerSample; sound->sampleRate = wav.samplesPerSecond; sound->buffer = lmAlloc(gAssetAllocator, sound->bufferSize); bool dataCopySuccess = load_wav(charBuff, bufferLen, (uint8_t*)sound->buffer, NULL); if (!dataCopySuccess) { lmLogError(gSoundAssetGroup, "Failed to copy wav data"); loom_asset_soundDtor(sound); return 0; } } else { lmLogError(gSoundAssetGroup, "Failed to identify sound buffer by magic number!"); loom_asset_soundDtor(sound); return 0; } *dtor = loom_asset_soundDtor; if(!sound->buffer) { lmLogError(gSoundAssetGroup, "Sound load failed due to this cryptic reason: %s", "(unknown)"); lmFree(gAssetAllocator, sound); return 0; } lmLogDebug(gSoundAssetGroup, "Sound allocation: %d bytes", sound->bufferSize); return sound; }
void DLLEXPORT assetAgent_run(IdleCallback idleCb, LogCallback logCb, FileChangeCallback changeCb) { loom_log_initialize(); platform_timeInitialize(); stringtable_initialize(); loom_net_initialize(); // Put best effort towards closing our listen socket when we shut down, to // avoid bugs on OSX where the OS won't release it for a while. #if LOOM_PLATFORM == LOOM_PLATFORM_OSX || LOOM_PLATFORM == LOOM_PLATFORM_LINUX atexit(shutdownListenSocket); signal(SIGINT, shutdownListenSocketSignalHandler); #endif // Set up mutexes. gActiveSocketsMutex = loom_mutex_create(); gFileScannerLock = loom_mutex_create(); gCallbackLock = loom_mutex_create(); // Note callbacks. gLogCallback = logCb; gFileChangeCallback = changeCb; utString *sdkPath = optionGet("sdk"); if (sdkPath != NULL) TelemetryServer::setClientRootFromSDK(sdkPath->c_str()); const char *ltcPath = getenv("LoomTelemetry"); if (ltcPath != NULL) TelemetryServer::setClientRoot(ltcPath); if (optionEquals("telemetry", "true")) TelemetryServer::start(); // Set up the log callback. loom_log_addListener(fileWatcherLogListener, NULL); lmLogDebug(gAssetAgentLogGroup, "Starting file watcher thread..."); gFileWatcherThread = loom_thread_start((ThreadFunction)fileWatcherThread, NULL); lmLogDebug(gAssetAgentLogGroup, " o OK!"); lmLogDebug(gAssetAgentLogGroup, "Starting socket listener thread..."); gSocketListenerThread = loom_thread_start((ThreadFunction)socketListeningThread, NULL); lmLogDebug(gAssetAgentLogGroup, " o OK!"); // Loop till it's time to quit. while (!gQuitFlag) { // Serve the idle callback. if (idleCb) { idleCb(); } // And anything in the queue. while (CallbackQueueNote *cqn = dequeueCallback()) { if (!cqn) { break; } // Issue the call. if (cqn->type == QNT_Change) { gFileChangeCallback(cqn->text.c_str()); } else if (cqn->type == QNT_Log) { gLogCallback(cqn->text.c_str()); } else { lmAssert(false, "Unknown callback queue note type."); } // Clean it up. //free((void *)cqn->text); lmDelete(NULL, cqn); } // Pump any remaining socket writes loom_net_pump(); // Poll at about 60hz. loom_thread_sleep(16); } // Clean up the socket. shutdownListenSocket(); }