// This is the entry point for the file watcher thread. It scans local files // for changes and processes the diffs with processFileEntryDeltas. static int fileWatcherThread(void *payload) { // Start with a sane state so we don't stream everything. utArray<FileEntry> *oldState = generateFileState("."); // Loop forever looking for changes. for ( ; ; ) { int startTime = platform_getMilliseconds(); utArray<FileEntry> *newState = generateFileState("."); utArray<FileEntryDelta> *deltas = compareFileEntries(oldState, newState); int endTime = platform_getMilliseconds(); if (endTime - startTime > 250) { lmLogWarn(gAssetAgentLogGroup, "Scanning files took %dms, consider removing unused files", endTime - startTime); } processFileEntryDeltas(deltas); lmDelete(NULL, deltas); // Make the new state the old state and clean up the old state. lmDelete(NULL, oldState); oldState = newState; // Wait a bit so we don't saturate disk or CPU. loom_thread_sleep(gFileCheckInterval); } }
void loom_asset_waitForConnection(int msToWait) { // be extra agressive before starting up gAssetServerConnectTryInterval = 10; int startTime = platform_getMilliseconds(); while (!gAssetConnectionOpen && (platform_getMilliseconds() - startTime) < msToWait) { loom_asset_pump(); loom_thread_sleep(10); } // Go back to pinging every 3 seconds gAssetServerConnectTryInterval = 3000; }
static int pumpTillLoaded(int timeoutMs) { int startTime = platform_getMilliseconds(); while (loom_asset_queryPendingLoads()) { loom_asset_pump(); if (platform_getMilliseconds() - startTime > timeoutMs) { return 0; } } return 1; }
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); } }
void loom_tick() { // Mark the main thread for NativeDelegates. On some platforms this // may change so we remark every frame. NativeDelegate::markMainThread(); NativeDelegate::executeDeferredCalls(LoomApplication::getRootVM()->VM()); performance_tick(); profilerBlock_t p = { "loom_tick", platform_getMilliseconds(), 8 }; if (LoomApplication::getReloadQueued()) { LoomApplication::reloadMainAssembly(); } else { LSLuaState *vm = LoomApplication::getRootVM(); if (vm) { // https://theengineco.atlassian.net/browse/LOOM-468 // decouple debugger enabled from connection time // as the debugger matures this may change a bit if (LoomApplicationConfig::waitForDebugger() > 0) { vm->invokeStaticMethod("system.debugger.DebuggerClient", "update"); } LoomApplication::ticks.invoke(); } } loom_asset_pump(); platform_HTTPUpdate(); GFX::Texture::tick(); lualoom_gc_update(LoomApplication::getRootVM()->VM()); if(Loom2D::Stage::smMainStage) Loom2D::Stage::smMainStage->invokeRenderStage(); finishProfilerBlock(&p); }
// Service our connection to the asset agent. static void loom_asset_serviceServer() { loom_mutex_lock(gAssetServerSocketLock); // Try to connect to the asset server if we aren't already, and it is set. if ((gAssetServerSocket == NULL) && ((ASSET_STREAM_HOST != NULL) && (strlen(ASSET_STREAM_HOST) > 0)) && ((platform_getMilliseconds() - gAssetServerLastConnectTryTime) > gAssetServerConnectTryInterval)) { lmLog(gAssetLogGroup, "Attempting to stream assets from %s:%d", ASSET_STREAM_HOST, ASSET_STREAM_PORT); gAssetServerLastConnectTryTime = platform_getMilliseconds(); gAssetServerSocket = loom_net_openTCPSocket(ASSET_STREAM_HOST, ASSET_STREAM_PORT, 0); gAssetConnectionOpen = false; loom_asset_notifyPendingCountChange(); loom_mutex_unlock(gAssetServerSocketLock); return; } if ((gAssetServerSocket != NULL) && (gAssetConnectionOpen == false)) { // We are waiting on the connection, see if it's writable... If not, return. if (loom_net_isSocketWritable(gAssetServerSocket) == 0) { loom_mutex_unlock(gAssetServerSocketLock); return; } if (loom_net_isSocketDead(gAssetServerSocket) == 1) { // Might be DOA, ie, connect failed. lmLog(gAssetLogGroup, "Failed to connect to asset server %s:%d", ASSET_STREAM_HOST, ASSET_STREAM_PORT); loom_net_closeTCPSocket(gAssetServerSocket); gAssetServerSocket = NULL; lmSafeDelete(NULL, gAssetProtocolHandler); gAssetConnectionOpen = false; loom_asset_notifyPendingCountChange(); loom_mutex_unlock(gAssetServerSocketLock); return; } lmLog(gAssetLogGroup, "Successfully connected to asset server %s:%d!", ASSET_STREAM_HOST, ASSET_STREAM_PORT); // Do this now to avoid clobbering error state and seeing the socket as // "open" when it is really dead. loom_net_enableSocketKeepalive(gAssetServerSocket); gAssetConnectionOpen = true; loom_asset_notifyPendingCountChange(); // Make sure we have a protocol handler. if (!gAssetProtocolHandler) { gAssetProtocolHandler = lmNew(NULL) AssetProtocolHandler(gAssetServerSocket); gAssetProtocolHandler->registerListener(lmNew(NULL) AssetProtocolFileMessageListener()); gAssetProtocolHandler->registerListener(lmNew(NULL) AssetProtocolCommandListener()); } loom_mutex_unlock(gAssetServerSocketLock); return; } // See if the socket is dead, and if so, clean up. if ((gAssetServerSocket != NULL) && (loom_net_isSocketDead(gAssetServerSocket) == 1)) { lmLog(gAssetLogGroup, "Lost connection to asset server."); loom_net_closeTCPSocket(gAssetServerSocket); gAssetServerSocket = NULL; lmSafeDelete(NULL, gAssetProtocolHandler); gAssetConnectionOpen = false; loom_asset_notifyPendingCountChange(); loom_mutex_unlock(gAssetServerSocketLock); return; } // Bail if we don't have a connection. if (!gAssetServerSocket || !gAssetConnectionOpen) { loom_mutex_unlock(gAssetServerSocketLock); return; } // Ping if we need to. if (platform_getMilliseconds() - gAssetServerLastPingTime > gAssetServerPingInterval) { gAssetProtocolHandler->sendPing(); gAssetServerLastPingTime = platform_getMilliseconds(); } // Service the asset server connection. gAssetProtocolHandler->process(); loom_mutex_unlock(gAssetServerSocketLock); }
// 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!"); } }
static int __stdcall scaleImageOnDisk_body(void *param) { // Grab our arguments. RescaleNote *rn = (RescaleNote *)param; const char *outPath = rn->outPath.c_str(); const char *inPath = rn->inPath.c_str(); int outWidth = rn->outWidth; int outHeight = rn->outHeight; bool preserveAspect = rn->preserveAspect; // Load the image. We always work in 4 components (rgba). int t0 = platform_getMilliseconds(); loom_asset_image *lai = NULL; // Load async since we're in a background thread. while ((lai = (loom_asset_image *)loom_asset_lock(inPath, LATImage, 0)) == NULL) { loom_thread_yield(); } int imageX = lai->width; int imageY = lai->height; stbi_uc *imageBits = (stbi_uc *)lai->bits; lmLog(gGFXTextureLogGroup, "Image setup took %dms", t0 - platform_getMilliseconds()); int t1 = platform_getMilliseconds(); // Resize to fit within the specified size preserving aspect ratio, if flag is set. if (preserveAspect) { float scaleX = float(outWidth) / float(imageX); float scaleY = float(outHeight) / float(imageY); float actualScale = (scaleX < scaleY) ? scaleX : scaleY; outWidth = (int)(imageX * actualScale); outHeight = (int)(imageY * actualScale); lmLog(gGFXTextureLogGroup, "Scale to %d %d due to scale %f %f actual=%f", outWidth, outHeight, scaleX, scaleY, actualScale); } // Build a buffer for byte->float conversions... Resampler::Sample *buffRed = (Resampler::Sample *)malloc(sizeof(Resampler::Sample) * imageX); Resampler::Sample *buffGreen = (Resampler::Sample *)malloc(sizeof(Resampler::Sample) * imageX); Resampler::Sample *buffBlue = (Resampler::Sample *)malloc(sizeof(Resampler::Sample) * imageX); // And the downsampled image. Give a slight margin because the scaling routine above can give // up to outWidth inclusive as an output value. stbi_uc *outBuffer = (stbi_uc *)malloc(sizeof(stbi_uc) * 3 * (outWidth + 1) * (outHeight + 1)); // Set up the resamplers, reusing filter constants. const char *pFilter = "blackman"; float filter_scale = 1.0; Resampler resizeR(imageX, imageY, outWidth, outHeight, Resampler::BOUNDARY_CLAMP, 0.0f, 1.0f, pFilter, NULL, NULL, filter_scale, filter_scale); Resampler resizeG(imageX, imageY, outWidth, outHeight, Resampler::BOUNDARY_CLAMP, 0.0f, 1.0f, pFilter, resizeR.get_clist_x(), resizeR.get_clist_y(), filter_scale, filter_scale); Resampler resizeB(imageX, imageY, outWidth, outHeight, Resampler::BOUNDARY_CLAMP, 0.0f, 1.0f, pFilter, resizeR.get_clist_x(), resizeR.get_clist_y(), filter_scale, filter_scale); int resultY = 0; lmLog(gGFXTextureLogGroup, "Resample setup took %dms", t1 - platform_getMilliseconds()); int t2 = platform_getMilliseconds(); // Process each row of the image. for (int y = 0; y < imageY; y++) { // Deinterleave each row. for (int x = 0; x < imageX; x++) { buffRed[x] = Resampler::Sample(imageBits[(y * imageX * 4) + (x * 4) + 0]) / Resampler::Sample(255.f); buffGreen[x] = Resampler::Sample(imageBits[(y * imageX * 4) + (x * 4) + 1]) / Resampler::Sample(255.f); buffBlue[x] = Resampler::Sample(imageBits[(y * imageX * 4) + (x * 4) + 2]) / Resampler::Sample(255.f); } // Submit to resampler. lmAssert(resizeR.put_line(buffRed), "bad red"); lmAssert(resizeG.put_line(buffGreen), "bad green"); lmAssert(resizeB.put_line(buffBlue), "bad blue"); // If there are results, reinterleave and consume them. while (resizeR.check_line() && resizeG.check_line() && resizeB.check_line() && resultY < outHeight) { const Resampler::Sample *outRowR = resizeR.get_line(); const Resampler::Sample *outRowG = resizeG.get_line(); const Resampler::Sample *outRowB = resizeB.get_line(); if (outRowR || outRowG || outRowB) { lmAssert(outRowR && outRowG && outRowB, "Somehow got one line without others!"); } else { break; } // Find the row for output. stbi_uc *imageOutBits = outBuffer + (resultY * outWidth * 3); resultY++; for (int i = 0; i < outWidth; i++) { imageOutBits[i * 3 + 0] = int(outRowR[i] * Resampler::Sample(255.f)); imageOutBits[i * 3 + 1] = int(outRowG[i] * Resampler::Sample(255.f)); imageOutBits[i * 3 + 2] = int(outRowB[i] * Resampler::Sample(255.f)); } // Every hundred lines post an update. if (resultY % 100 == 0) { postResampleEvent(outPath, (float)resultY / (float)outHeight); } } } lmLog(gGFXTextureLogGroup, "Resample took %dms", t2 - platform_getMilliseconds()); // Write it back out. int t3 = platform_getMilliseconds(); jpge::compress_image_to_jpeg_file(outPath, outWidth, outHeight, 3, outBuffer); lmLog(gGFXTextureLogGroup, "JPEG output took %dms", t3 - platform_getMilliseconds()); // Post completion event. postResampleEvent(outPath, 1.0); // Free everything! loom_asset_unlock(inPath); free(buffRed); free(buffGreen); free(buffBlue); free(outBuffer); delete rn; return 0; }
void loom_tick() { if (atomic_load32(&gLoomTicking) < 1) { // Signal that the app has really stopped execution if (atomic_load32(&gLoomPaused) == 0) { atomic_store32(&gLoomPaused, 1); } // Sleep for longer while paused. // Since graphics aren't running in a paused state, there is no yielding // we would otherwise run in a busy loop without sleeping. loom_thread_sleep(30); return; } atomic_store32(&gLoomPaused, 0); Telemetry::beginTick(); LOOM_PROFILE_START(loom_tick); LSLuaState *vm = NULL; vm = LoomApplication::getReloadQueued() ? NULL : LoomApplication::getRootVM(); // Mark the main thread for NativeDelegates. On some platforms this // may change so we remark every frame. NativeDelegate::markMainThread(); if (vm) NativeDelegate::executeDeferredCalls(vm->VM()); performance_tick(); profilerBlock_t p = { "loom_tick", platform_getMilliseconds(), 17 }; if (LoomApplication::getReloadQueued()) { LoomApplication::reloadMainAssembly(); } else { if (vm) { // https://theengineco.atlassian.net/browse/LOOM-468 // decouple debugger enabled from connection time // as the debugger matures this may change a bit if (LoomApplicationConfig::waitForDebugger() > 0) { vm->invokeStaticMethod("system.debugger.DebuggerClient", "update"); } LoomApplication::ticks.invoke(); } } loom_asset_pump(); platform_HTTPUpdate(); GFX::Texture::tick(); if (Loom2D::Stage::smMainStage) Loom2D::Stage::smMainStage->invokeRenderStage(); finishProfilerBlock(&p); LOOM_PROFILE_END(loom_tick); LOOM_PROFILE_ZERO_CHECK() Telemetry::endTick(); }
static int getTime(lua_State *L) { lua_pushnumber(L, platform_getMilliseconds()); return 1; }