Skip to content

Commit 0936597

Browse files
committed
Update code for require("timer").add({type:"EXEC",fn:...}) to look up the function that is being called. It's much safer than storing the reference directly as defrags could have moved the function
1 parent 2de6698 commit 0936597

File tree

5 files changed

+38
-21
lines changed

5 files changed

+38
-21
lines changed

src/jsdevices.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ typedef enum {
8787
#ifdef BLUETOOTH
8888
EV_BLUETOOTH_PENDING, // Tasks that came from the Bluetooth Stack in an IRQ
8989
#endif
90-
EV_RUN_INTERRUPT_JS, ///< Run some JavaScript code. See EXEC_RUN_INTERRUPT_JS. data is JsVarRef of code to run
90+
EV_RUN_INTERRUPT_JS, ///< Run some JavaScript code. See EXEC_RUN_INTERRUPT_JS. JS function to run is in hiddenRoot.TMFN[data]
9191
EV_CUSTOM, ///< Custom event (First byte is IOCustomEventFlags to determine the event type)
9292
#ifdef BANGLEJS
9393
EV_BANGLEJS, // sent whenever Bangle.js-specific data needs to be queued

src/jsinteractive.c

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2207,13 +2207,14 @@ void jsiHandleIOEventForConsole(uint8_t *eventData, int eventLen) {
22072207
/** This is called if a EV_RUN_INTERRUPT_JS is received, or when a EXEC_RUN_INTERRUPT_JS is set.
22082208
It executes JavaScript code that was pushed to the queue by a require("timer").add({type:"EXEC", fn:myFunction... */
22092209
static void jsiOnRunInterruptJSEvent(const uint8_t *eventData, unsigned int eventLen) {
2210-
for (unsigned int i=0;i<eventLen-1;i+=2) {
2211-
JsVarRef codeRef = *(JsVarRef *)&eventData[i];
2212-
if (codeRef) {
2213-
JsVar *code = jsvLock(codeRef);
2214-
if (!jsvIsFunction(code)) return; // invalid code - maybe things got moved?
2215-
jsvUnLock(jspExecuteFunction(code, execInfo.root, 0, NULL));
2216-
jsvUnLock(code);
2210+
for (unsigned int i=0;i<eventLen;i++) {
2211+
uint8_t timerIdx = eventData[i];
2212+
JsVar *timerFns = jsvObjectGetChildIfExists(execInfo.hiddenRoot, JSI_TIMER_RUN_JS_NAME);
2213+
if (timerFns) {
2214+
JsVar *fn = jsvGetArrayItem(timerFns, timerIdx);
2215+
if (jsvIsFunction(fn))
2216+
jsvUnLock(jspExecuteFunction(fn, execInfo.root, 0, NULL));
2217+
jsvUnLock2(timerFns, fn);
22172218
}
22182219
}
22192220
}

src/jsinteractive.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#define JSI_TIMERS_NAME "timers"
2222
#define JSI_DEBUG_HISTORY_NAME "dbghist"
2323
#define JSI_HISTORY_NAME "history"
24+
#define JSI_TIMER_RUN_JS_NAME "TMFN"
2425
#define JSI_INIT_CODE_NAME "init" ///< used to temporarily store initialisation JS code for state in save()
2526
#define JSI_LOAD_CODE_NAME "load" ///< used to temporarily store the name of a file to load from Storage when load(xyz) is used
2627
#define JSI_JSFLAGS_NAME "flags"

src/jstimer.c

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,6 @@ bool utilTimerRemoveTask(int id) {
366366
}
367367
// move 'end' pointer back
368368
utilTimerTasksTail = (utilTimerTasksTail+1) & (UTILTIMERTASK_TASKS-1);
369-
utilTimerTaskInfo[id].type = UET_NONE;
370369
jshInterruptOn();
371370
return true;
372371
}
@@ -703,6 +702,13 @@ void jstOnCustomEvent(IOEventFlags eventFlags, uint8_t *data, int dataLen) {
703702
if ((customFlags&EVC_TYPE_MASK)==EVC_TIMER_FINISHED) {
704703
int id = customFlags >> EVC_DATA_SHIFT;
705704
if (utilTimerTaskInfo[id].type & UET_FINISHED) {
705+
if (utilTimerTaskInfo[id].type == (UET_FINISHED|UET_EXECUTE)) { // if EXEC with a JS function, free the function
706+
JsVar *timerFns = jsvObjectGetChildIfExists(execInfo.hiddenRoot, JSI_TIMER_RUN_JS_NAME);
707+
if (timerFns) {
708+
jsvRemoveArrayItem(timerFns, id);
709+
jsvUnLock(timerFns);
710+
}
711+
}
706712
utilTimerTaskInfo[id].type = UET_NONE;
707713
}
708714
}

src/jswrap_timer.c

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ This replaces `E.dumpTimers()` and `Pin.writeAtTime`
3434
*/
3535

3636
static void jswrap_timer_queue_interrupt_js(JsSysTime time, void* userdata) {
37-
JsVarRef code = (JsVarRef)(size_t)userdata;
38-
jshPushIOCharEvents(EV_RUN_INTERRUPT_JS, (char*)&code, sizeof(code));
37+
uint8_t timerIdx = (uint8_t)(size_t)userdata;
38+
jshPushIOCharEvents(EV_RUN_INTERRUPT_JS, (char*)&timerIdx, 1);
3939
execInfo.execute |= EXEC_RUN_INTERRUPT_JS;
4040
jshHadEvent();
4141
}
@@ -131,20 +131,25 @@ JsVar *jswrap_timer_get(int id) {
131131
typeStr="EXEC";
132132
#ifndef SAVE_ON_FLASH
133133
if (task.data.execute.fn == jswrap_timer_queue_interrupt_js) {
134-
JsVar *fn = jsvLock((JsVarRef)(size_t)task.data.execute.userdata);
135-
jsvObjectSetChildAndUnLock(obj, "fn", fn);
134+
int timerIdx = (int)(size_t)task.data.execute.userdata;
135+
JsVar *timerFns = jsvObjectGetChildIfExists(execInfo.hiddenRoot, JSI_TIMER_RUN_JS_NAME);
136+
if (timerFns) {
137+
jsvObjectSetChildAndUnLock(obj, "fn", jsvGetArrayItem(timerFns, timerIdx));
138+
jsvUnLock(timerFns);
139+
}
136140
} else {
137141
jsvObjectSetChildAndUnLock(obj, "ptr", jsvNewFromInteger((size_t)task.data.execute.fn));
138142
jsvObjectSetChildAndUnLock(obj, "userdata", jsvNewFromInteger((size_t)task.data.execute.userdata));
139143
}
140144
#endif
141145
break;
146+
default: break; // could be other things if ORDed with UET_FINISHED - ignore them (fixed warning)
142147
}
143148
#ifndef SAVE_ON_FLASH
144149
if (UET_EVENT_HAS_PINS(task.type)) {
145150
JsVar *pinsArr = jsvNewEmptyArray();
146151
int pinCount = UET_PIN_COUNT(task.type);
147-
for (int i=0;i<UTILTIMERTASK_PIN_COUNT;i++)
152+
for (int i=0;i<pinCount;i++)
148153
if (task.data.set.pins[i] != PIN_UNDEFINED)
149154
jsvArrayPushAndUnLock(pinsArr, jsvNewFromPin(task.data.set.pins[i]));
150155
jsvObjectSetChildAndUnLock(obj, "pins", pinsArr);
@@ -213,11 +218,17 @@ require("timer").add({
213218
})
214219
// eg. execute myFunction in 100ms, then 200ms thereafter
215220
require("timer").add({
216-
type:"EXEC", fn:myFunction,
221+
type:"EXEC", fn: () => LED.toggle(),
217222
time:100,
218223
interval:200,
219224
});
220225
```
226+
227+
**Note:** `require("timer").add({type:"EXEC",fn:...})` differs from `setInterval`/`setTimeout` in that
228+
it is scheduled using a hardware timer. When the timer fires, JavaScript that's executing will be
229+
paused at the next statement and the JS will be executed right away. This can be great for things
230+
like scanning out screens where you don't want your execution to be paused even if you're executing
231+
JavaScript code.
221232
*/
222233
int jswrap_timer_add(JsVar *timer) {
223234
JsVarFloat time=0, interval=0;
@@ -265,13 +276,11 @@ int jswrap_timer_add(JsVar *timer) {
265276
task->data.set.value = value;
266277
} else if (evtType == UET_EXECUTE) {
267278
if (jsvIsFunction(fn)) { // if a function is passed we use EXEC_RUN_INTERRUPT_JS
268-
if (jsvGetRefs(fn) == 0) {
269-
jsExceptionHere(JSET_ERROR, "Function passed to timer must be referenced elsewhere");
270-
jsvUnLock(fn);
271-
return -1;
272-
}
279+
JsVar *timerFns = jsvObjectGetChild(execInfo.hiddenRoot, JSI_TIMER_RUN_JS_NAME, JSV_ARRAY); // set the timer function in the hidden scope so we can look it up later
280+
if (timerFns) jsvSetArrayItem(timerFns, idx, fn);
281+
jsvUnLock(timerFns);
273282
ptr = (size_t)jswrap_timer_queue_interrupt_js;
274-
userdata = jsvGetRef(fn);
283+
userdata = idx;
275284
}
276285
task->data.execute.fn = (UtilTimerTaskExecFn)(size_t)ptr;
277286
task->data.execute.userdata = (void*)(size_t)userdata;

0 commit comments

Comments
 (0)