Example #1
0
// 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);
    }
}
Example #2
0
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;
}
Example #3
0
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;
}
Example #4
0
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);
}
Example #6
0
// 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);
}
Example #7
0
// 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!");
    }
}
Example #8
0
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;
}
Example #9
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();

}
Example #10
0
    static int getTime(lua_State *L)
    {
        lua_pushnumber(L, platform_getMilliseconds());

        return 1;
    }