view src/dmtimeline.c @ 1785:86d10d5d4915

Fix case where DMGrowBuf is growing backwards and needs to be reallocated in dmGrowBufRealloc() and the data is moved to the "end" of the newly grown buffer. Previously we used clrsize as data size, but that is (in retrospect) obviously incorrect. Use old buffer size instead.
author Matti Hamalainen <ccr@tnsp.org>
date Wed, 13 Jun 2018 01:39:06 +0300
parents 28fe5b0925dc
children 7190f4fbc0dd
line wrap: on
line source

/*
 * dmlib
 * -- Timeline file loading and timeline processing/execution
 * Programmed and designed by Matti 'ccr' Hamalainen
 * (C) Copyright 2012-2015 Tecnic Software productions (TNSP)
 */
#include "dmengine.h"


static BOOL dmLoadFloatValue(DMResource *res, DMFloat *val)
{
    char tmp[DT_FLOAT_STORE_SIZE + 1];
    if (!dmf_read_str(res, &tmp, DT_FLOAT_STORE_SIZE))
        return FALSE;

    tmp[DT_FLOAT_STORE_SIZE] = 0;
    *val = atof(tmp);
    return TRUE;
}


static int dmLoadTimelinePoints(DMResource *res, DMTimelinePoints *points, int type)
{
    Uint32 npoints;

    // Get number of points
    if (!dmf_read_le32(res, &npoints))
        return DMERR_FREAD;

    // Calculate and allocate space
    points->npoints = npoints;
    points->nallocated = ((npoints / 16) + 1) * 16;

    points->points = dmCalloc(points->nallocated, sizeof(DMTimelinePoint));
    if (points->points == NULL)
        return DMERR_MALLOC;

    // Read points
    for (int point = 0; point < points->npoints; point++)
    {
        DMTimelinePoint *pt = &(points->points[point]);
        Uint32 ptype, ptime;
        if (!dmf_read_le32(res, &ptype) ||
            !dmf_read_le32(res, &ptime))
            return DMERR_FREAD;

        switch (type)
        {
            case EFPT_INT:
                {
                    Uint32 tmp;
                    if (!dmf_read_le32(res, &tmp))
                        return DMERR_FREAD;
                    pt->vint = tmp;
                }
                break;

            case EFPT_FLOAT:
                if (!dmLoadFloatValue(res, &pt->vfloat))
                    return DMERR_FREAD;
                break;

            case EFPT_VECTOR:
                if (!dmLoadFloatValue(res, &pt->vector.x) ||
                    !dmLoadFloatValue(res, &pt->vector.y) ||
                    !dmLoadFloatValue(res, &pt->vector.z) ||
                    !dmLoadFloatValue(res, &pt->vector.W))
                    return DMERR_FREAD;
                break;

            case EFPT_MATRIX:
                {
                    int x, y;
                    for (y = 0; y < DM_MATRIX_SIZE; y++)
                    for (x = 0; x < DM_MATRIX_SIZE; x++)
                    {
                        if (!dmLoadFloatValue(res, &(pt->matrix.m[y][x])))
                            return DMERR_FREAD;
                    }
                }
                break;
        }

        pt->type = ptype;
        pt->time = ptime;
    }
    return DMERR_OK;
}


static int dmLoadTimelineEventParam(DMResource *res, DMTimelineEventParam *param)
{
    int err;
    DMFTimelineEventParam hdr;
    Uint16 len;

    if (!dmf_read_str(res, &hdr.name, sizeof(hdr.name)) ||
        !dmf_read_le32(res, &hdr.type))
        return DMERR_FREAD;

    hdr.name[sizeof(hdr.name) - 1] = 0;

    param->name = dm_strdup(hdr.name);
    param->type = hdr.type;

    switch (param->type)
    {
        case EFPT_INT:
        case EFPT_FLOAT:
        case EFPT_VECTOR:
        case EFPT_MATRIX:
            if ((err = dmLoadTimelinePoints(res, &param->pts, param->type)) != DMERR_OK)
                return err;
            break;

        case EFPT_STRING:
            if (!dmf_read_le16(res, &len))
                return DMERR_FREAD;

            param->vstr = dmMalloc((unsigned int)len + 1);
            if (!dmf_read_str(res, param->vstr, len))
                return DMERR_FREAD;

            param->vstr[len] = 0;
            break;
    }

    return DMERR_OK;
}


static int dmLoadTimelineEvent(DMResource *res, DMTimelineEvent **pevent)
{
    DMFTimelineEvent hdr;
    DMTimelineEvent *event;
    if ((*pevent = event = dmMalloc0(sizeof(DMTimelineEvent))) == NULL)
        return DMERR_MALLOC;

    // Get basic event data
    if (!dmf_read_le32(res, &hdr.start) ||
        !dmf_read_le32(res, &hdr.duration) ||
        !dmf_read_str(res, &hdr.effectName, sizeof(hdr.effectName)) ||
        !dmf_read_le32(res, &hdr.nparams))
        return DMERR_FREAD;

    hdr.effectName[sizeof(hdr.effectName) - 1] = 0;

    if (hdr.nparams > DT_MAX_EFFECT_PARAMS)
    {
        return dmError(DMERR_INVALID_DATA,
            "Invalid number of parameters, %d > %d ('%s' @ %d:%d)\n",
            hdr.nparams, DT_MAX_EFFECT_PARAMS, hdr.effectName, hdr.start, hdr.duration);
    }

    event->start     = hdr.start;
    event->duration  = hdr.duration;
    event->nparams   = hdr.nparams;
    event->effect    = engineFindEffect(hdr.effectName, hdr.nparams);
    if (event->effect == NULL)
    {
        return dmError(DMERR_INVALID_DATA,
            "No matching registered effect found for '%s'.\n",
            hdr.effectName);
    }

    event->params    = dmCalloc(event->nparams, sizeof(DMTimelineEventParam));
    if (event->params == NULL)
    {
        return dmError(DMERR_MALLOC,
            "Could not allocate memory for timeline event parameters.\n");
    }

    for (int param = 0; param < event->nparams; param++)
    {
        int err;
        if ((err = dmLoadTimelineEventParam(res, &(event->params[param]))) != DMERR_OK)
            return err;
    }

    return DMERR_OK;
}


static int dmLoadTimelineCurve(DMResource *res, DMTimelineCurve *curve)
{
    int err;

    curve->enabled = dmfgetc(res);
    if ((err = dmLoadTimelinePoints(res, &(curve->points), EFPT_FLOAT)) != DMERR_OK)
        return err;

    return DMERR_OK;
}


static int dmLoadTimelineTrack(DMResource *res, DMTimelineTrack **ptrack)
{
    int err;
    DMFTimelineTrack hdr;
    DMTimelineTrack *track;
    if ((*ptrack = track = dmMalloc0(sizeof(DMTimelineTrack))) == NULL)
        return DMERR_MALLOC;

    if (!dmf_read_le32(res, &hdr.index) ||
        !dmf_read_le32(res, &hdr.layer) ||
        !dmf_read_str(res, &hdr.name, sizeof(hdr.name)) ||
        !dmf_read_byte(res, &hdr.enabled) ||
        !dmf_read_le32(res, &hdr.nevents))
        return DMERR_FREAD;

    if (hdr.nevents >= 4096)
        return DMERR_INVALID_DATA;

    if ((track->events = dmCalloc(hdr.nevents, sizeof(DMTimelineEvent *))) == NULL)
        return DMERR_MALLOC;

    hdr.name[sizeof(hdr.name) - 1] = 0;
    track->name    = dm_strdup(hdr.name);
    track->enabled = hdr.enabled;
    track->nevents = hdr.nevents;

    for (int event = 0; event < track->nevents; event++)
    {
        if ((err = dmLoadTimelineEvent(res, &(track->events[event]))) != DMERR_OK)
            return err;
    }

    if ((err = dmLoadTimelineCurve(res, &(track->composite))) != DMERR_OK)
        return err;

    return DMERR_OK;
}


int dmLoadTimeline(DMResource *res, DMTimeline **ptl)
{
    int err;
    DMFTimeline hdr;
    DMTimeline *tl;

    if ((*ptl = tl = dmMalloc0(sizeof(DMTimeline))) == NULL)
        return DMERR_MALLOC;

    // Read and check header
    if (!dmf_read_str(res, &hdr.magic, sizeof(hdr.magic)))
        return DMERR_FREAD;

    if (memcmp(hdr.magic, DT_MAGIC_ID, sizeof(hdr.magic)) != 0)
        return DMERR_INVALID_DATA;

    if (!dmf_read_str(res, &hdr.name, sizeof(hdr.name)) ||
        !dmf_read_le32(res, &hdr.ntracks) ||
        !dmf_read_le32(res, &hdr.duration))
        return DMERR_FREAD;

    if (hdr.ntracks >= 64)
        return DMERR_INVALID_DATA;

    // Allocate track pointers
    tl->tracks = (DMTimelineTrack **) dmCalloc(hdr.ntracks, sizeof(DMTimelineTrack *));
    if (tl->tracks == NULL)
        return DMERR_MALLOC;

    // Copy rest
    hdr.name[sizeof(hdr.name) - 1] = 0;
    tl->name     = dm_strdup(hdr.name);
    tl->duration = hdr.duration;
    tl->ntracks  = hdr.ntracks;

    // Read tracks
    for (int track = 0; track < tl->ntracks; track++)
    {
        if ((err = dmLoadTimelineTrack(res, &(tl->tracks[track]))) != DMERR_OK)
            return err;
    }

    return DMERR_OK;
}


void dmFreeTimelinePoints(DMTimelinePoints *points)
{
    dmFree(points->points);
    points->points = NULL;
    points->npoints = points->nallocated = 0;
}


void dmFreeTimelineEventParam(DMTimelineEventParam *param)
{
    dmFree(param->name);
    dmFree(param->vstr);
    dmFreeTimelinePoints(&(param->pts));
}


void dmFreeTimelineEvent(DMTimelineEvent *event)
{
    if (event != NULL)
    {
        for (int param = 0; param < event->nparams; param++)
            dmFreeTimelineEventParam(&(event->params[param]));
        dmFree(event->params);
    }
}


void dmFreeTimelineTrack(DMTimelineTrack *track)
{
    if (track != NULL)
    {
        dmFree(track->name);

        for (int event = 0; event < track->nevents; event++)
            dmFreeTimelineEvent(track->events[event]);

        dmFree(track->events);

        dmFreeTimelinePoints(&(track->composite.points));
    }
}


void dmFreeTimeline(DMTimeline *tl)
{
    if (tl != NULL)
    {
        for (int track = 0; track < tl->ntracks; track++)
            dmFreeTimelineTrack(tl->tracks[track]);

        dmFree(tl->tracks);
        dmFree(tl->name);
    }
}


static void dmFreePreparedEventGroup(DMPreparedEventGroup *group)
{
    if (group != NULL)
    {
        dmFree(group->events);
        dmFree(group);
    }
}


void dmFreePreparedTimelineData(DMPreparedTimeline *ptl)
{
    if (ptl != NULL)
    {
        for (int group = 0; group < ptl->ngroups; group++)
        {
            dmFreePreparedEventGroup(ptl->groups[group]);
            ptl->groups[group] = NULL;
        }

        dmFree(ptl->groups);
        ptl->groups = NULL;
    }
}


/* Prepare a loaded timeline for execution. Creates the "stacked" structure
 * of timeline data for efficient rendering.
 */
int dmAddSplitPreparedEventGroup(DMPreparedEventGroup **groups, DMTimelineTrack *track, DMTimelineEvent *event)
{
    DMPreparedEventGroup *node;

    for (node = *groups; node != NULL; node = node->next)
    {
    }

    return DMERR_OK;
}


int dmPrepareTimeline(DMTimeline *tl, DMPreparedTimeline *ptl)
{
    int group, ntrack, event, err;
    DMPreparedEventGroup *groups = NULL, *node;

    // Free previous data
    dmFreePreparedTimelineData(ptl);

    // Process tracks
    for (ntrack = 0; ntrack < tl->ntracks; ntrack++)
    {
        DMTimelineTrack *track = tl->tracks[ntrack];
        for (event = 0; event < track->nevents; event++)
        {
            if ((err = dmAddSplitPreparedEventGroup(&groups, track, track->events[event])) != DMERR_OK)
                return err;
        }
    }

    // Compute number of groups
    ptl->ngroups = 0;
    for (node = groups; node != NULL; node = node->next)
        ptl->ngroups++;

    // Allocate linear array for fast access
    ptl->groups = dmMalloc(sizeof(DMPreparedEventGroup) * ptl->ngroups);
    if (ptl->groups == NULL)
        return DMERR_MALLOC;

    // Store pointers in the array
    for (group = 0, node = groups; node != NULL; node = node->next)
        ptl->groups[group++] = node;

    return DMERR_OK;
}


/* Seeks to specified position in the timeline. The execution function
 * only handles monotonously increasing time, going backwards will not work
 * there correctly, thus to seek freely this function must be used.
 */
int dmSeekTimeline(DMPreparedTimeline *tl, int time)
{
    return DMERR_OK;
}


/* "Executes", or rather renders a frame on the specified timeline position.
 */
int dmExecuteTimeline(DMPreparedTimeline *tl, DMEngineData *engine, int time)
{
    return DMERR_OK;
}