I'm trying to create a program that does something, waits for a set amount of time does another thing, then waits again. However, what actually happens is the program waits at the beginning then does both things without any delay between them.
var start, current
function setup() {
createCanvas(500, 550);
}
function draw() {
background(220);
print('a');
wait(500);
print('b');
wait(500);
}
function wait(time)
{
start = millis()
do
{
current = millis();
}
while(current < start + time)
}
The draw() function is executed several times per seconds (around 60 times per second, see framerate in the doc). This is also what we call a "draw loop".
Your logic seems to be very sequential (do this, then wait and to that, then wait and do another thing...), and maybe you should consider an other flow for your program than the draw loop.
If you want animation, the easy answer would be to stick to the answer provided by Rabbid76.
(read and compare elapsed time millis every time the draw loop executes).
If you want one-time events (things that happen only once when a wanted duration is reached), you should look into Promises (or async-await functions), also known as asynchronicity.
This subject can be confusing for beginners, but is very important in javascript.
Here is an example:
(link with p5 editor)
// notice we are NOT using the draw() loop here
function setup()
{
createCanvas(400, 400);
background('tomato')
// try commenting-out one of these:
doScheduleThings();
doAsyncAwaitThings();
}
// you can wait for a Promise to return with the javascript 'then' keyword
// the function execution's is not stopped but each '.then(...)' schedules a function for when the Promise 'sleep(...)' is resolved
function doScheduleThings()
{
sleep(2000).then(function() {
fill('orange')
ellipse(30,30, 50, 50)
})
sleep(1000).then(function() {
fill('yellow')
ellipse(130,30, 50, 50)
})
}
// you can also wait for a Promise to return with an async-await function
// the function's execution is stopped while waiting for each Promise to resolve
async function doAsyncAwaitThings()
{
await sleep(4000)
fill('blue')
rect(200,200, 50, 50)
await sleep(1000)
fill('cyan')
rect(300,200, 50, 50)
}
// a custom 'sleep' or wait' function, that returns a Promise that resolves only after a timeout
function sleep(millisecondsDuration)
{
return new Promise((resolve) => {
setTimeout(resolve, millisecondsDuration);
})
}
You cannot wait in the draw callback. The canvas is just updated when after draw was executed. You must evaluate the time in draw:
function draw() {
background(220);
let ms = millis()
if (ms < 500) {
// [...]
}
else if (ms < 1000) {
// [...]
}
else {
// [...]
}
}
As mentioned, the problem in your attempt is that you're waiting inside the draw() loop. This doesn't work, because draw() is going to be called continually.
A simple way to do it is the following:
function setup() {
//...
}
let task_done = false;
let last_done = 0;
function draw() {
const delay = 1000 //ms
if(!task_done) {
/* do something */
doSomething();
task_done = true;
last_done = millis();
}
else {
if(millis() - last_done > delay) {
task_done = false;
}
}
}
function doSomething() {
//...
}
It only executes something every delay ms.
Related
I am making a platformer type game and need help on knowing how to delay the time before going downwards on the jump function i have tried the thread function and it didn't work I do have a question related to that but if there are any alternatives please let me know that would be very much appreciate it.
Here's a way to delay something without stopping the program. It's a class I wrote a long time ago, when I was still a student, so I'm not proud of it but I think that it's a good way to introduce someone to millis().
Long story short, this class will let you enter a number of milliseconds it will wait for and you can, in your loop, test to see if the delay is expired or not. Here's the class:
class Delay {
int limit;
Delay (int l) {
limit = millis() + l;
}
boolean expired () {
return millis() > limit;
}
}
And here's an example of a program waiting 5000 millis (5 seconds) after a mouse click before letting you click again. It's pretty self-explanatory, but don't hesitate to ask questions about it:
int count;
Delay delay;
void setup() {
size(400, 400);
delay = new Delay(0);
}
void draw() {
background(255);
//
if (!delay.expired()) {
fill(color(255, 0, 0));
rect(100, 100, 100, 100);
fill(0);
text("Time before click is allowed: " + (delay.limit - millis()), 20, 20);
}
text("Time since last click: " + (millis() - count), 20, 40);
}
void mouseClicked() {
// only lets you start a new delay if the last one is expired
if (delay.expired()) {
delay = new Delay(5000);
count = millis();
}
}
class Delay {
int limit;
Delay (int l) {
limit = millis() + l;
}
boolean expired () {
return millis() > limit;
}
}
Knowing how to time stuff is important, but timing stuff like jumps in a platformer often include other considerations, like touching the ground or not. Still, I hope that this will help you.
Have fun!
I am using code like this on a particle electron to report pulse counts from a flow meter on my kegerator to the particle cloud:
void meterInterrupt(void) {
detachInterrupt(pin);
ticks++;
cloudPending = 1;
attachInterrupt(pin, meterInterrupt, FALLING);
}
void publishStatus() {
if (!cloudPending) {
return;
}
cloudPending = 0;
getStatus(&statusMessage);
// status message contains number of ticks since last publish
bool published = Particle.publish("Ticks", statusMessage, PRIVATE);
if (published) {
resetMeters();
lastPublish = millis();
}
}
void loop() {
if ((millis() - lastPublish) >= 1000) {
publishStatus();
}
}
When I curl the event log into my terminal, I see two events for the first publish like so:
event: Ticks
data: {"data":"ticks:1","ttl":60,"published_at":"2018-07-03T22:35:01.008Z","coreid":"420052000351353337353037"}
event: hook-sent/Ticks
data: {"data":"","ttl":60,"published_at":"2018-07-03T22:35:01.130Z","coreid":"particle-internal"}
event: Ticks
data: {"data":"ticks:46","ttl":60,"published_at":"2018-07-03T22:35:01.193Z","coreid":"420052000351353337353037"}
event: hook-sent/Ticks
data: {"data":"","ttl":60,"published_at":"2018-07-03T22:35:01.303Z","coreid":"particle-internal"}
I don't see how this could happen. Why didn't it just report "ticks:47"? What am I missing?
UPDATE:
I did some further testing and noticed that Particle.publish is returning false the first time when it is actually completing successfully. Is this a timeout issue? The time difference between these publishes is only about 200ms.
OK, This is at least a partial answer.
It appears that Particle.publish is asynchronous. It returns the promise of an answer that starts out as false only eventually becomes true when/if the action is actually completed. If I wait an indeterminate amount of time (say delay(10)) after Particle.publish and before checking the return code, the return value will indicate the actual success or failure of the publish. My code cannot work because the ticks that are counted while I wait will be deleted when I reset the meters. WITH_ACK gives me the same behavior.
I will have to modify my code such that no ticks are lost during the long running Particle.publish . I am thinking that each statusMessage should go onto a list until it is ack'ed by the server.
FINAL ANSWER:
I modified the code to close the window during which I can receive ticks that will then be wiped out when I reset the counters. I do this by capturing the ticks into an array and then resetting the tick counter (meter). I am using a library called PublishQueueAsyncRK (cudos to rickkas7 This library is great!) so I can just fire it and forget it. Check it out on github.
void publishStatus() {
unsigned int counters[NUM_METERS];
unsigned int pending;
for (int i = 0; i < NUM_METERS; i++) {
meter_t *meter = &meters[i];
counters[i] = meter->ticks;
pending += counters[i];
resetMeter(i);
}
if (pending) {
String statusReport;
for (int i = 0; i < NUM_METERS; i++) {
statusReport.concat(String::format("%i:%u|", i+1, counters[i]));
}
publishReport(statusReport);
lastPublished = millis();
}
}
void publishReport(String report) {
if (report != "") {
publishQueue.publish("PourLittleTicks", report, PRIVATE);
}
}
void loop() {
if ((millis() - lastPublished) >= PUBLISH_INTERVAL) {
publishStatus();
}
}
I am trying to get my animation to cycle through two images when a directional key is pressed. Currently it switches images on each key press. I have been looking at tutorials and understand that I need some kind of timer to measure each frame time but everything I have tried to implement in to my code so far has failed so I am just posting my code that works at the moment.
Could anyone explain to me how to go about implementing this please?
void Frog::up(sf::Event event)
{
sf::IntRect frogUpAnimation[iNumFrames];
frogUpAnimation[0] = sf::IntRect(13, 362, 21, 23);
frogUpAnimation[1] = sf::IntRect(46, 367, 21, 23);
if (event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::Up)
{
audio.frogjumpsound();
frogSprite.move(0.0f, -55.0f);
iScoreCounter = iScoreCounter + 10;
iCurrentFrame++;
if (iCurrentFrame >= iNumFrames) iCurrentFrame = 0;
frogSprite.setTextureRect(frogUpAnimation[iCurrentFrame]);
}
}
int main()
{
sf::RenderWindow window(sf::VideoMode(800, 800), "Frogger");
window.setFramerateLimit(60);
sf::Clock timer;
float fFrameTime = 1.0f / 60.0f;
float fElapsedTime;
sf::Event event;
Game game;
Frog frog;
Timer countdown;
Text text;
Audio gameaudio;
gameaudio.music();
while (window.isOpen())
{
while (window.pollEvent(event))
{
if (event.type == sf::Event::Closed)
{
window.close();
}
if (event.type == sf::Event::KeyPressed)
{
game.processKeyPress(event.key.code);
}
frog.up(event);
frog.down(event);
frog.left(event);
frog.right(event);
} // Event loop
countdown.gametime();
fElapsedTime = timer.getElapsedTime().asSeconds();
if (fElapsedTime > fFrameTime)
{
timer.restart();
}
//Update
game.checkPads(&frog);
game.checkWin();
game.gameOver();
frog.scorecounter(&countdown);
frog.update(fElapsedTime);
game.update(fElapsedTime);
text.update(fElapsedTime);
countdown.update(fElapsedTime);
game.collision(&frog);
// Drawing
window.clear();
window.draw(game);
window.draw(frog);
window.draw(countdown);
window.draw(text);
window.display();
} // main loop
}
First, I would only call 'frog.up()', 'frog.left()', etc. when the proper key is pressed. Otherwise, you're calling potentially 4 functions each iteration which do absolutely nothing.
if(sf::Keyboard::isKeyPressed(sf::Keyboard::Right) {
frog.right();
}
else if(sf::Keyboard::isKeyPressed(sf::Keyboard::Left) {
frog.left();
}
As for your timer issues, your fElapsedTime and fFrameTime look a little funky. Take a look at https://en.sfml-dev.org/forums/index.php?topic=10913.0 but I really recommend this guide: https://github.com/SFML/SFML/wiki/Source:-AnimatedSprite
Does the fileselected function in this code complete its execution even before while loop execution?
void setup()
{
size(800, 600);
selectInput("Select a file to process:", "fileSelected");
while(data==null)
{
delay(1000);
}
}
void fileselected()
{
*
*
*
*
}
How do I make the draw function wait until it receives the necessary arguments to run?
does setup and draw function run parallel in processing
No. First the setup() function is called and completes, then the draw() function is called 60 times per second.
does the fileselected function completes its execution even before while loop execution.
The fileSelected() function will be called when the user selects a file. You really shouldn't call the delay() function in a loop like that.
How do I make the draw function to wait until it receives the necessary arguments to run.
Something like this:
boolean fileLoaded = false;
void setup(){
size(800, 600);
selectInput("Select a file to process:", "fileSelected");
}
void fileSelected(File selection){
fileLoaded = true;
}
void draw(){
if(!fileLoaded){
//short-circuit and stop the function
return;
}
}
You could go a step further and use the noLoop() and loop() functions. More info can be found in the reference.
I have some code that saves data using Breeze and reports progress over multiple saves that is working reasonably well.
However, sometimes a save will timeout, and I'd like to retry it once automatically. (Currently the user is shown an error and has to retry manually)
I am struggling to find an appropriate way to do this, but I am confused by promises, so I'd appreciate some help.
Here is my code:
//I'm using Breeze, but because the save takes so long, I
//want to break the changes down into chunks and report progress
//as each chunk is saved....
var surveys = EntityQuery
.from('PropertySurveys')
.using(manager)
.executeLocally();
var promises = [];
var fails = [];
var so = new SaveOptions({ allowConcurrentSaves: false});
var count = 0;
//...so I iterate through the surveys, creating a promise for each survey...
for (var i = 0, len = surveys.length; i < len; i++) {
var query = EntityQuery.from('AnsweredQuestions')
.where('PropertySurveyID', '==', surveys[i].ID)
.expand('ActualAnswers');
var graph = manager.getEntityGraph(query)
var changes = graph.filter(function (entity) {
return !entity.entityAspect.entityState.isUnchanged();
});
if (changes.length > 0) {
promises.push(manager
.saveChanges(changes, so)
.then(function () {
//reporting progress
count++;
logger.info('Uploaded ' + count + ' of ' + promises.length);
},
function () {
//could I retry the fail here?
fails.push(changes);
}
));
}
}
//....then I use $q.all to execute the promises
return $q.all(promises).then(function () {
if (fails.length > 0) {
//could I retry the fails here?
saveFail();
}
else {
saveSuccess();
}
});
Edit
To clarify why I have been attempting this:
I have an http interceptor that sets a timeout on all http requests. When a request times out, the timeout is adjusted upwards, the user is displayed an error message, telling them they can retry with a longer wait if they wish.
Sending all the changes in one http request is looking like it could take several minutes, so I decided to break the changes down into several http requests, reporting progress as each request succeeds.
Now, some requests in the batch might timeout and some might not.
Then I had the bright idea that I would set a low timeout for the http request to start with and automatically increase it. But the batch is sent asynchronously with the same timeout setting and the time is adjusted for each failure. That is no good.
To solve this I wanted to move the timeout adjustment after the batch completes, then also retry all requests.
To be honest I'm not so sure an automatic timeout adjustment and retry is such a great idea in the first place. And even if it was, it would probably be better in a situation where http requests were made one after another - which I've also been looking at: https://stackoverflow.com/a/25730751/150342
Orchestrating retries downstream of $q.all() is possible but would be very messy indeed. It's far simpler to perform retries before aggregating the promises.
You could exploit closures and retry-counters but it's cleaner to build a catch chain :
function retry(fn, n) {
/*
* Description: perform an arbitrary asynchronous function,
* and, on error, retry up to n times.
* Returns: promise
*/
var p = fn(); // first try
for(var i=0; i<n; i++) {
p = p.catch(function(error) {
// possibly log error here to make it observable
return fn(); // retry
});
}
return p;
}
Now, amend your for loop :
use Function.prototype.bind() to define each save as a function with bound-in parameters.
pass that function to retry().
push the promise returned by retry().then(...) onto the promises array.
var query, graph, changes, saveFn;
for (var i = 0, len = surveys.length; i < len; i++) {
query = ...; // as before
graph = ...; // as before
changes = ...; // as before
if (changes.length > 0) {
saveFn = manager.saveChanges.bind(manager, changes, so); // this is what needs to be tried/retried
promises.push(retry(saveFn, 1).then(function() {
// as before
}, function () {
// as before
}));
}
}
return $q.all(promises)... // as before
EDIT
It's not clear why you might want to retry downsteam of $q.all(). If it's a matter of introducing some delay before retrying, the simplest way would be to do within the pattern above.
However, if retrying downstream of $q.all() is a firm requirement, here's a cleanish recursive solution that allows any number of retries, with minimal need for outer vars :
var surveys = //as before
var limit = 2;
function save(changes) {
return manager.saveChanges(changes, so).then(function () {
return true; // true signifies success
}, function (error) {
logger.error('Save Failed');
return changes; // retry (subject to limit)
});
}
function saveChanges(changes_array, tries) {
tries = tries || 0;
if(tries >= limit) {
throw new Error('After ' + tries + ' tries, ' + changes_array.length + ' changes objects were still unsaved.');
}
if(changes_array.length > 0) {
logger.info('Starting try number ' + (tries+1) + ' comprising ' + changes_array.length + ' changes objects');
return $q.all(changes_array.map(save)).then(function(results) {
var successes = results.filter(function() { return item === true; };
var failures = results.filter(function() { return item !== true; }
logger.info('Uploaded ' + successes.length + ' of ' + changes_array.length);
return saveChanges(failures), tries + 1); // recursive call.
});
} else {
return $q(); // return a resolved promise
}
}
//using reduce to populate an array of changes
//the second parameter passed to the reduce method is the initial value
//for memo - in this case an empty array
var changes_array = surveys.reduce(function (memo, survey) {
//memo is the return value from the previous call to the function
var query = EntityQuery.from('AnsweredQuestions')
.where('PropertySurveyID', '==', survey.ID)
.expand('ActualAnswers');
var graph = manager.getEntityGraph(query)
var changes = graph.filter(function (entity) {
return !entity.entityAspect.entityState.isUnchanged();
});
if (changes.length > 0) {
memo.push(changes)
}
return memo;
}, []);
return saveChanges(changes_array).then(saveSuccess, saveFail);
Progress reporting is slightly different here. With a little more thought it could be made more like in your own answer.
This is a very rough idea of how to solve it.
var promises = [];
var LIMIT = 3 // 3 tris per promise.
data.forEach(function(chunk) {
promises.push(tryOrFail({
data: chunk,
retries: 0
}));
});
function tryOrFail(data) {
if (data.tries === LIMIT) return $q.reject();
++data.tries;
return processChunk(data.chunk)
.catch(function() {
//Some error handling here
++data.tries;
return tryOrFail(data);
});
}
$q.all(promises) //...
Two useful answers here, but having worked through this I have concluded that immediate retries is not really going to work for me.
I want to wait for the first batch to complete, then if the failures are because of timeouts, increase the timeout allowance, before retrying failures.
So I took Juan Stiza's example and modified it to do what I want. i.e. retry failures with $q.all
My code now looks like this:
var surveys = //as before
var successes = 0;
var retries = 0;
var failedChanges = [];
//The saveChanges also keeps a track of retries, successes and fails
//it resolves first time through, and rejects second time
//it might be better written as two functions - a save and a retry
function saveChanges(data) {
if (data.retrying) {
retries++;
logger.info('Retrying ' + retries + ' of ' + failedChanges.length);
}
return manager
.saveChanges(data.changes, so)
.then(function () {
successes++;
logger.info('Uploaded ' + successes + ' of ' + promises.length);
},
function (error) {
if (!data.retrying) {
//store the changes and resolve the promise
//so that saveChanges can be called again after the call to $q.all
failedChanges.push(data.changes);
return; //resolved
}
logger.error('Retry Failed');
return $q.reject();
});
}
//using map instead of a for loop to call saveChanges
//and store the returned promises in an array
var promises = surveys.map(function (survey) {
var changes = //as before
return saveChanges({ changes: changes, retrying: false });
});
logger.info('Starting data upload');
return $q.all(promises).then(function () {
if (failedChanges.length > 0) {
var retries = failedChanges.map(function (data) {
return saveChanges({ changes: data, retrying: true });
});
return $q.all(retries).then(saveSuccess, saveFail);
}
else {
saveSuccess();
}
});