Make update loop in sync with music and notes in Phaser - phaser-framework

I'm trying to make some notes appearing when the music hits a certain time in Phaser, but when I log the "hit times" in the console, it only show up sometimes.
I have an object of "notes", the key being the time I expect the note to show :
{
1377: {
jam: 1,
duration: 0.40
}
}
with 1464 notes.
But, in the update loop, if I do something like this :
update () {
if (music && music.currentTime) {
if (notes[music.currentTime]) {
console.log('notes[music.currentTime].jam', notes[music.currentTime].jam)
}
}
}
It logs only some of the notes, randomly.
Do you have any idea why ?

This is probably because music.currentTime is incrementing by ~16 ms on each update, so it can skip specific time keys from your notes object. Other than that I believe that the time can also be a floating point value, so it won't exactly match your keys in the notes variable.
An alternate way to implement what you want would be to change format of the notes variable to an array so it could be accessed later in a different manner:
var notes = [
...
{'start': 1377, 'jam': 1, 'duration': 0.40},
{'start': 2456, 'jam': 1, 'duration': 0.30},
...
];
// Index of the first note that will be played.
// Will be incremented by 1 or more with some update() calls,
// depending on the time that passed.
var nextNote = 0;
function update() {
// Process all notes that are now in the past
// compared to the current time of the playing music,
// but skip notes that have been already played before.
while (music && nextNote < notes.length && notes[nextNote].start <= music.currentTime) {
console.log(notes[nextNote]);
nextNote += 1;
}
}
For this method to work, the notes array must hold start times in an ascending order.

Related

Dynamic Table updates "too late" ReactJS

my problem is, that I have a table which should update everytime when the user chooses something from a dropdown component. The problem now is that my table updates "too late" in the frontend. So when the user chooses an option for the first time nothing will happen. Then when the user chooses an option for the second time from the dropdown component, the table will show the data from the option he has picked before. If the user chooses an option for the 3rd time, the table will show the data from the second one and so on.
So how can I fix this? I work with ReactJS and Semantic UI
My Code:
This renders the Row for the existing data
renderTableData() {
return this.state.songs.map((song, index) => {
const { id, nr, songname, link } = song
return (
<Table.Row key={id}>
<Table.Cell>{nr}</Table.Cell>
<Table.Cell>{songname}</Table.Cell>
<Table.Cell>{link}</Table.Cell>
</Table.Row>
)
})
}
The Code in the main render() function of React (Its shown correctly, expect that the data is "outdated":
`<Table>
<Table.Header>
<Table.Row>
<Table.HeaderCell width={1}>Nr</Table.HeaderCell>
<Table.HeaderCell width={2}>Songname</Table.HeaderCell>
<Table.HeaderCell width={1}>Link</Table.HeaderCell>
</Table.Row>
</Table.Header>
{this.renderTableData()}
</Table>`
The code when the option from the dropdown gets changed:
onChangeDropdown(e) {
this.setState({game: e.target.textContent}, ()=>{
this.state.songs.length = 0;
for(var i = 0; i< this.state.musicData.length;i++){
if(this.state.musicData[i].game == this.state.game){
for(var j = 0; j<this.state.musicData[i].songs.length;j++){
this.state.songs.push({id: j+1, nr: j+1, songname: this.state.musicData[i].songs[j].name, link: this.state.musicData[i].songs[j].link})
}
break;
}
}
this.renderTableData()
})
}
The game variable in this.setState is correct and also the for-loop works as expected when the user changes the dropdown option, I already checked it with the debugger
I hope you can help me out there, ty
is not that is updating too late, is that you are mutating the state without using setState so React doesn't know what changed, you should refactor your code to always use setState to update the state, not push, something like this:
onChangeDropdown(e) {
this.setState((currentState) => {
const newSongs = [];
const game = e.target.textContent;
musicData.forEach((data) => {
if (data.game === game) {
musicData.songs.forEach((song, index) => {
newSongs.push({
id: index + 1,
nr: index + 1,
songname: song.name,
link: song.link,
});
});
}
});
return {
...currentState,
game,
songs: newSongs,
};
});
}
I changed your for loops to use forEach, less complexity, easier to read
Here is what I did:
create a empty array to store the selected songs (newSongs)
loop all the music data and then loop all the songs inside each item in music data
add the songs from the selected game into newSongs
return newSongs + game to update the selected game, ...currentState is to preserve the other parts of the state between changes
So every time the dropodown changes, I create a new array and run the logic
The setState callback can return an object to replace whole state, so before that you can do any calculation you need to.
Updating the state in React is asyncronous, that's one of the reasons you can't mutate the state directly and need to use setState any time you need to update it

How can i run some code at an exact timestamp?

I have a REST API in a backend with an array of objects with a timestamp (time when something happens in-game) and a value.
{"timestamp":1623320102097,"crops":[0,5,9]}
How can i do run something when time is equals to that timestamp ?
I presume that timestamps are in msec. Probably this should do the trick:
let diffMsec = obj.timestamp - new Date().getTime();
if (diffMsec > 0) {
setTimeout(function() {
/* do your stuff */
}, diffMsec);
}
Keep in mind though, that it is not guaranteed that timeout will be invoked at the exact time.

Phaser 3 restarting tween with updated start values

I have 4 reusable tweens that should move the target relative to its current position.
Each of them is basically the same as this example one, but for each cardinal direction:
goLeft = this.tweens.add({
targets: gamePieceSprite,
x: {value: '-=64'},
duration: 1000,
paused: true
});
Since I want to be able to use these tweens multiple times, I use the restart() method, which I want to play the tween from the sprite's current position, but even when I change the position of the sprite, when I call the function to restart the tween it plays from the coordinates at which it first played and not where the targeted sprite currently is.
var pathCounter = 0;
testPathText.on('pointerdown', function(){
if (pathCounter % 2 == 0)
{
gamePieceSprite.setX(startTile.x);
gamePieceSprite.setY(startTile.y);
}
else
{
if (levelStartRotation[level] == '1')
{
goLeft.restart();
}
else if (levelStartRotation[level] == '2')
{
goUp.restart();
}
else if (levelStartRotation[level] == '3')
{
goDown.restart();
}
else if (levelStartRotation[level] == '4')
{
goRight.restart();
}
}
pathCounter++;
});
Is there any way for me to update the start point of the tween so that restarting it doesn't just start from where it started before?
I was also frustrated trying to figure this one out (and it looks like there still isn't a solution, almost one year later). I have not found any methods or best practices to do so yet, but by digging through the tween object, I discovered you can access the properties of a tween in its data array. Since it's an array, if you have multiple properties, you will have to find the property by comparing it's key value. If you only have one, simply get data[0]. The property has a start field that you can set.
In your case, try:
...
if (levelStartRotation[level] == '1')
{
goLeft.data[0].start = startTile.x;
goLeft.restart();
}
...

How to come around Firebase Realtime Database's server side timestamp volatility when one wants to compare timestamps on updates?

After reading the docs on ServerValue.TIMESTAMP, I was under the impression that once the object hits the database, the timestamp placeholder evaluates once and remains the same, but this was not the case for me:
// Example on Node:
> const db = f.FIREBASE_APP.database();
> const timestamp = f.FIREBASE_APP.database.ServerValue.TIMESTAMP;
> const ref = db.ref('/test');
> ref.on(
... 'child_added',
... function(snapshot) {
..... console.log(`Timestamp from listener: ${snapshot.val().timestamp}`);
..... }
... )
> var child_key = "";
> ref.push({timestamp: timestamp}).then(
... function(thenable_ref) {
..... child_key = thenable_ref.key;
..... }
... );
Timestamp from listener: 1534373384299
> ref.child(child_key).once('value').then(
... function(snapshot) {
..... console.log(`Timestamp after querying: ${snapshot.val().timestamp}`);
..... }
... );
> Timestamp after querying: 1534373384381
> 1534373384299 < 1534373384381
true
The timestamp is different when queried from the on listener and it is different during a later query.
Is this like this by design and I just missed some parts of the documentation? If this is the case, when does the ServerValue.TIMESTAMP stabilize?
I am building a CQRS/ES library on the Realtime Database, and just wanted to avoid the expected_version (or sequence numbers) of events.
UPDATE
The proof for Frank's explanation below:
/* `db`, `ref` and `timestamp` are defined above,
and the test path ("/test") has been deleted
from DB beforehand to avoid noise.
*/
> ref.on(
... 'child_added',
... function(snapshot) {
..... console.log(`Timestamp from listener: ${snapshot.val().timestamp}`);
..... }
... )
> ref.on(
... 'value',
... function(snapshot) {
..... console.log(snapshot.val());
..... }
... )
> ref.push({timestamp: timestamp}); null;
Timestamp from listener: 1534434409034
{ '-LK2Pjd8FS_L8hKqIpiE': { timestamp: 1534434409034 } }
{ '-LK2Pjd8FS_L8hKqIpiE': { timestamp: 1534434409114 } }
Bottom line is, if one needs to rely on immutable server side timestamps, keep this in mind, or work around it.
When you perform the ref.push({timestamp: timestamp}) the Firebase client immediately makes an estimate of the timestamp on the client and fires an event for that locally. It then send the command off to the server.
Once the Firebase client receives the response from the server, it checks if the actual timestamp is different from its estimate. If it is indeed different, the client fires reconciliatory events.
You can most easily see this by attaching your value listener before setting the value. You'll see it fire with both the initial estimates value, and the final value from the server.
Also see:
How to use the Firebase server timestamp to generate date created?
Trying to convert Firebase timestamp to NSDate in Swift
firebase.database.ServerValue.TIMESTAMP return an Object
CAVEAT: After wasting another day, the ultimate solution is not to use Firebase server timestamps at all, if you have to compare them in a use case that is similar to the one below. When the events come in fast enough, the second 'value' update may not trigger at all.
One solution, to the double-update condition Frank describes in his answer, is to get the final server timestamp value is (1) to embed an on('event', ...) listener inside an on('child_added', ...) and (2) remove the on('event', ...) listener as soon as the specific use case permits.
> const db = f.FIREBASE_APP.database();
> const ref = db.ref('/test');
> const timestamp = f.FIREBASE_APP.database.ServerValue.TIMESTAMP;
> ref.on(
'child_added',
function(child_snapshot) {
console.log(`Timestamp in 'child_added': ${child_snapshot.val().timestamp}`);
ref.child(child_snapshot.key).on(
'value',
function(child_value_snapshot) {
// Do a timestamp comparison here and remove `on('value',...)`
// listener here, but keep in mind:
// + it will fire TWICE when new child is added
// + but only ONCE for previously added children!
console.log(`Timestamp in embedded 'event': ${child_value_snapshot.val().timestamp}`);
}
)
}
)
// One child was already in the bank, when above code was invoked:
Timestamp in 'child_added': 1534530688785
Timestamp in embedded 'event': 1534530688785
// Adding a new event:
> ref.push({timestamp: timestamp});null;
Timestamp in 'child_added': 1534530867511
Timestamp in embedded 'event': 1534530867511
Timestamp in embedded 'event': 1534530867606
In my CQRS/ES case, events get written into the "/event_store" path, and the 'child_added' listener updates the cumulated state whenever new events come in, where each event has a ServerValue.TIMESTAMP. The listener compares the new event's and the state's timestamp whether the new event should be applied or it already has been (this mostly matters when the server has been restarted to build the internal in-memory state). Link to the full implementation, but here's a shortened outline on how single/double firing has been handled:
event_store.on(
'child_added',
function(event_snapshot) {
const event_ref = event_store.child(event_id)
event_ref.on(
'value',
function(event_value_snapshot){
const event_timestamp = event_value_snapshot.val().timestamp;
if ( event_timestamp <= state_timestamp ) {
// === 1 =======
event_ref.off();
// =============
} else {
var next_state = {};
if ( event_id === state.latest_event_id ) {
next_state["timestamp"] = event_timestamp;
Object.assign(state, next_state);
db.ref("/state").child(stream_id).update(state);
// === 2 =======
event_ref.off();
// =============
} else {
next_state = event_handler(event_snapshot, state);
next_state["latest_event_id"] = event_id;
Object.assign(state, next_state);
}
}
}
);
}
);
When the server is restarted, on('child_added', ...) goes through all events already in the "/event_store", attaching on('value',...) dynamically on all children and compares the events` timestamps to the current state's.
If the event is older than the age of the current state (event_timestamp < state_timestamp is true), the only action is detaching the 'value' listener . This callback will be fired once as the ServerValue.TIMESTAMP placeholder has already been resolved once in the past.
Otherwise the event is newer, which means that it hasn't been applied yet to the current state and ServerValue.TIMESTAMP also hasn't been evaluated yet, causing the callback to fire twice. To handle the double update, this block saves the actual child's key (i.e., event_id here) to the state (to latest_event_id) and compares it to the incoming event's key (i.e., event_id):

How to specify different delays between slides in bxslider

Ran across the following problem in bxslider- how can you apply different delays between slides in the auto show?
I came up with the following solution which I will show here:
in the jquery.bxslider.js replace:
el.startAuto = function(preventControlUpdate){
// if an interval already exists, disregard call
if(slider.interval) return;
// create an interval
slider.interval = setInterval(function(){
slider.settings.autoDirection == 'next' ? el.goToNextSlide() : el.goToPrevSlide();
}, slider.settings.pause);
// if auto controls are displayed and preventControlUpdate is not true
if (slider.settings.autoControls && preventControlUpdate != true) updateAutoControls('stop');
}
With
/**EDITS: By CRB - techdude **/
el.startAuto = function(preventControlUpdate){
el.continueAuto();
}
el.continueAuto = function(){
//get how long the current slide should stay
var duration = slider.children.eq(parseInt(slider.active.index)).attr("duration");
if(duration == ""){
duration = slider.settings.pause;
} else {
duration = parseInt(duration);
}
console.log(duration);
// create a timeout
slider.timer = setTimeout(function(){
slider.settings.autoDirection == 'next' ? el.goToNextSlide() : el.goToPrevSlide();
el.continueAuto();
}, duration);
// if auto controls are displayed and preventControlUpdate is not true
if (slider.settings.autoControls && preventControlUpdate != true) updateAutoControls('stop');
}
//*End Edits*/
Then to change the duration of a slide, simply give its li tag a duration attribute like this:
where duration is the number of milliseconds for the slide to pause.
To set the default duration, simply use the pause: option in the settings:
$("element").bxSlider({
auto:true,
pause: 4000
};
Hope this helps. Maybe bx slider will even add it to a future version. :)
What are the you're using to pick this up? Any way you can put up a gist of it working?
Perhaps this will help clarify:
In principle, the way this works is I change the setInterval with a setTimeout so the interval can be changed each time.
The key to getting multiple elements to work on a page is to not use the slider.timer object, but probably to use the el.timer object so the line would read something like,
el.timer = setTimeout(function(){
slider.settings.autoDirection == 'next' ? el.goToNextSlide() : el.goToPrevSlide();
el.continueAuto();
}, duration);
Instead of
slider.timer = setTimeout(function(){
slider.settings.autoDirection == 'next' ? el.goToNextSlide() : el.goToPrevSlide();
el.continueAuto();
}, duration);
I haven't tested it with multiple sliders, but let me know if this works. That is the principle anyway. The only problem with this, however, is that I believe that you would need to modify the el.pause method to use the el.timer as well, otherwise the slideshow can't be paused. I think that was the reason I did it the way I did. However, it was a long time ago.

Resources