Skip to content

Commit

Permalink
add example taskobj.c to doc/6.externs to demonstrate the task API
Browse files Browse the repository at this point in the history
  • Loading branch information
Spacechild1 committed Jun 2, 2022
1 parent 4a27dc0 commit b396e58
Show file tree
Hide file tree
Showing 5 changed files with 229 additions and 8 deletions.
10 changes: 5 additions & 5 deletions doc/6.externs/makefile
Expand Up @@ -9,7 +9,7 @@ clean: ; rm -f *.pd_linux *.o

VC="D:\Program Files\Microsoft Visual Studio\Vc98"

pd_nt: obj1.dll obj2.dll obj3.dll obj4.dll obj5.dll dspobj~.dll
pd_nt: obj1.dll obj2.dll obj3.dll obj4.dll obj5.dll taskobj.dll dspobj~.dll

.SUFFIXES: .obj .dll

Expand All @@ -35,10 +35,10 @@ dspobj~.dll: dspobj~.c;
# ----------------------- LINUX i386 -----------------------

pd_linux: obj1.l_ia64 obj2.l_ia64 obj3.l_ia64 obj4.l_ia64 \
obj5.l_ia64 dspobj~.l_ia64
obj5.l_ia64 taskobj.l_ia64 dspobj~.l_ia64

pd_linux32: obj1.l_i386 obj2.l_i386 obj3.l_i386 obj4.l_i386 \
obj5.l_i386 dspobj~.l_i386
obj5.l_i386 taskobj.l_i386 dspobj~.l_i386

.SUFFIXES: .l_i386 .l_ia64

Expand All @@ -62,8 +62,8 @@ LINUXINCLUDE = -I../../src

# ----------------------- macOS -----------------------

pd_darwin: obj1.pd_darwin obj2.pd_darwin \
obj3.pd_darwin obj4.pd_darwin obj5.pd_darwin dspobj~.pd_darwin
pd_darwin: obj1.pd_darwin obj2.pd_darwin obj3.pd_darwin \
obj4.pd_darwin obj5.pd_darwin taskobj.pd_darwin dspobj~.pd_darwin

.SUFFIXES: .pd_darwin

Expand Down
180 changes: 180 additions & 0 deletions doc/6.externs/taskobj.c
@@ -0,0 +1,180 @@
/* This object shows how to run asynchronous tasks with the task API.
* The "read" method reads the content of the given file in the background.
* It outputs the file size on success and -1 on failure.
* With the "get" method you can read a single byte. */

#include "m_pd.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#ifdef _WIN32
#include <windows.h>
#else
#include <unistd.h>
#endif

static t_class *taskobj_class;

typedef struct _taskobj
{
t_object x_obj;
t_outlet *x_statusout;
t_outlet *x_byteout;
char *x_data;
int x_size;
t_task *x_current;
} t_taskobj;

typedef struct _taskdata
{
char filename[256];
char *data;
int size;
int err;
float ms;
} t_taskdata;

static void taskobj_worker(t_taskdata *x)
{
/* free old file data */
if (x->data)
free(x->data);
x->data = 0;
x->size = 0;
/* try to read new file data */
FILE *fp = fopen(x->filename, "rb");
if (fp)
{
char *data;
size_t size;
// get file size
fseek(fp, 0, SEEK_END);
size = ftell(fp);
fseek(fp, 0, SEEK_SET);
// allocate buffer
data = malloc(size);
if (data)
{
/* read file into buffer */
size_t result = fread(data, 1, size, fp);
if (result == size) /* success */
{
x->data = data;
x->size = size;
if (x->ms > 0)
{
/* sleep to simulate work */
#ifdef _WIN32
Sleep(x->ms);
#else
usleep(x->ms * 1000.0);
#endif
}
}
else /* fail */
{
x->err = errno;
free(data);
}
}
}
else /* catch error */
x->err = errno;
}

static void taskobj_callback(t_taskobj *x, t_taskdata *y)
{
if (x) /* task has been completed */
{
/* move file data to object */
x->x_data = y->data;
x->x_size = y->size;
/* task has completed and is now invalid.
do this BEFORE sending the message! */
x->x_current = 0;
/* notify */
if (x->x_data) /* success */
outlet_float(x->x_statusout, x->x_size);
else /* fail */
{
pd_error(x, "could not read file '%s': %s (%d)",
y->filename, strerror(y->err), y->err);
outlet_float(x->x_statusout, -1);
}
}
else /* task has been cancelled */
{
/* free the previous file data to avoid a memory leak! */
if (y->data)
free(y->data);
}
/* free the task data */
freebytes(y, sizeof(t_taskdata));
}

static void taskobj_read(t_taskobj *x, t_symbol *filename, t_floatarg ms)
{
t_taskdata *y = (t_taskdata *)getbytes(sizeof(t_taskdata));
y->err = 0;
y->ms = ms;
/* move old file data */
y->data = x->x_data;
y->size = x->x_size;
x->x_data = 0;
x->x_size = 0;
/* store file name */
snprintf(y->filename, sizeof(y->filename), "%s", filename->s_name);
/* cancel running task, so that we don't accidentally output
a message while we are still reading THIS file */
if (x->x_current)
task_cancel(x->x_current, 0);
/* schedule task */
x->x_current = task_sched((t_pd *)x, y,
(t_task_workfn)taskobj_worker, (t_task_callback)taskobj_callback);
}

static void taskobj_get(t_taskobj *x, t_floatarg f)
{
if (x->x_data)
{
int i = f;
if (i >= 0 && i < x->x_size)
outlet_float(x->x_byteout, x->x_data[i]);
else
pd_error(x, "index %d out of range!", i);
}
else
pd_error(x, "no file loaded!");
}

static void *taskobj_new(void)
{
t_taskobj *x = (t_taskobj *)pd_new(taskobj_class);
x->x_data = 0;
x->x_size = 0;
x->x_current = 0;
x->x_statusout = outlet_new(&x->x_obj, 0);
x->x_byteout = outlet_new(&x->x_obj, 0);
return x;
}

static void taskobj_free(t_taskobj *x)
{
/* cancel pending tasks */
task_join((t_pd *)x);
/* free file data */
if (x->x_data)
free(x->x_data);
}

void taskobj_setup(void)
{
taskobj_class = class_new(gensym("taskobj"),
(t_newmethod)taskobj_new, (t_method)taskobj_free,
sizeof(t_taskobj), 0, 0);
class_addmethod(taskobj_class, (t_method)taskobj_read,
gensym("read"), A_SYMBOL, A_DEFFLOAT, 0);
class_addmethod(taskobj_class, (t_method)taskobj_get,
gensym("get"), A_FLOAT, 0);
}
35 changes: 35 additions & 0 deletions doc/6.externs/test-taskobj.pd
@@ -0,0 +1,35 @@
#N canvas 523 166 455 399 12;
#X obj 33 77 openpanel;
#X obj 33 55 bng 15 250 50 0 empty empty empty 17 7 0 10 #fcfcfc #000000
#000000;
#X floatatom 33 340 8 0 0 0 - - -;
#X floatatom 79 315 5 0 0 0 - - -;
#X text 122 314 byte;
#X msg 124 253 get \$1;
#X floatatom 124 227 8 0 0 0 - - -;
#X text 31 27 try to open a huge file while running the test tone;
#X obj 48 133 realtime;
#X floatatom 48 157 8 0 0 0 - - -;
#X obj 33 313 t f b;
#X obj 33 101 t s b;
#X text 180 254 get a single byte;
#X text 98 341 file size (or -1 on failure);
#X obj 33 289 taskobj;
#X obj 33 188 pack s f;
#X msg 33 227 read \$1 \$2;
#X floatatom 103 189 6 0 0 0 - - -;
#X text 152 189 sleep (ms);
#X connect 0 0 11 0;
#X connect 1 0 0 0;
#X connect 5 0 14 0;
#X connect 6 0 5 0;
#X connect 8 0 9 0;
#X connect 10 0 2 0;
#X connect 10 1 8 1;
#X connect 11 0 15 0;
#X connect 11 1 8 0;
#X connect 14 0 10 0;
#X connect 14 1 3 0;
#X connect 15 0 16 0;
#X connect 16 0 14 0;
#X connect 17 0 15 1;
2 changes: 2 additions & 0 deletions doc/Makefile.am
Expand Up @@ -450,13 +450,15 @@ nobase_dist_libpd_DATA = \
./5.reference/writesf~-help.pd \
./6.externs/0.README.txt \
./6.externs/dspobj~.c \
./6.externs/taskobj.c \
./6.externs/makefile \
./6.externs/obj1.c \
./6.externs/obj2.c \
./6.externs/obj3.c \
./6.externs/obj4.c \
./6.externs/obj5.c \
./6.externs/test-dspobj~.pd \
./6.externs/test-taskobj.pd \
./6.externs/test-obj1.pd \
./6.externs/test-obj2.pd \
./6.externs/test-obj3.pd \
Expand Down
10 changes: 7 additions & 3 deletions src/s_task.c
Expand Up @@ -33,7 +33,7 @@

/* The task API is a safe and user-friendly way to run tasks in the background
* without interfering with the audio thread. This is intended for CPU intensive
* otherwise non-real-time safe operations, such as reading data from a file.
* or otherwise non-real-time safe operations, such as reading data from a file.
*
* See taskobj.c in doc/6.externs for a complete code example.
*
Expand Down Expand Up @@ -79,7 +79,7 @@
* true (1): check if the task is currently being executed and wait for its completion.
* false (0): return immediately without blocking.
* NOTE: Normally, your task data should not contain any mutable shared state,
* in which case is no need for any synchronization.
* in which case there is no need for any synchronization.
*
* returns: always 0.
* In the future, this may return additional information.
Expand Down Expand Up @@ -260,6 +260,7 @@ int sys_taskqueue_perform(t_pdinstance *pd, int nonblocking)
t_task *task = tasklist_pop(fromsched);
if (task)
{
/* set current task while locked */
queue->tq_current = task;
pthread_mutex_unlock(&fromsched->tl_mutex);

Expand All @@ -271,8 +272,11 @@ int sys_taskqueue_perform(t_pdinstance *pd, int nonblocking)
/* move task back to scheduler */
pthread_mutex_lock(&tosched->tl_mutex);
tasklist_push(tosched, task);
/* unset current task while locked */
queue->tq_current = 0;
pthread_mutex_unlock(&tosched->tl_mutex);
/* notify the scheduler thread because
it might be blocking in task_join() */
pthread_cond_signal(&tosched->tl_condition);

pthread_mutex_lock(&fromsched->tl_mutex);
Expand Down Expand Up @@ -446,7 +450,7 @@ t_task *task_sched(t_pd *owner, void *data,
#if PD_WORKERTHREADS
if (queue->tq_havethread)
{
/* push task to list and notify worker thread */
/* push task to list and notify one worker thread */
pthread_mutex_lock(&fromsched->tl_mutex);
tasklist_push(fromsched, task);
pthread_mutex_unlock(&fromsched->tl_mutex);
Expand Down

0 comments on commit b396e58

Please sign in to comment.