Meteor setInterval stacking - meteor

I am using Meteor.setInterval to count up to keep track of a players "action points" in a game. When they use an action (like attacking), it will decrease the amount. However, when an event is triggered(attacking) it updates players profile on my DB which triggers the setInterval to stack each time the DB is updated. It starts to counter faster and faster. edit This is the issue. I don't want it to count faster. I want only one interval to be running.
This was the closest solution I could find:
prevent javascript setInterval function stacking up
Didn't quite work and I couldn't find a different way to arrange the flags to only have one setInterval going.
Heres my helper: Each player has a different regen which is pulled from their profile. I've substituted fixed numbers for the interval and did a Session.get() when the stacking occurs, it stays the same. Instead of numbers I was just adding 'x' so it looks like a loading bar.
apTicker:function() {
Session.set('apRegen', Meteor.user().profile.apRegen);
Meteor.setInterval(function () {
if (Session.get('ap').length < 25) {
Session.set('ap', Session.get('ap')+"x");
}
}, Session.get('apRegen'))
},
Thank you.

This should do what you want:
var apTickerInterval; //To keep track of the interval
Session.set('apRegen', Meteor.user().profile.apRegen);
//...
apTicker : function()
{
if(apTickerInterval)
Meteor.clearInterval(apTickerInterval);
apTickerInterval = Meteor.setInterval(function () {
if (Session.get('ap').length < 25) {
Session.set('ap', Session.get('ap')+"x");
}
}, Session.get('apRegen'));
}

Related

Aframe Directly updating properties of aframe-particle-system-component in real time without setAttribute()

I'm using the aframe-particle-system-component, and was using .setAttribute() on the tick() function to update the particle system in real time, but it performed terribly, leading to almost instant memory-related crashes. Is there a way to directly access the maxAge, opacity, and enabled properties of the system? I'm assuming this is the best way to tackle this as the framework advises that .object3D is directly accessed for performance reasons. I would like to be able to access the particle system in a similar manner. Any help is appreciated.
tldr: here's an example with animated opacity, size, and some toggles. Switching max age seems to wait until all current particles are gone, though no word about it in the docs.
1. Gutting the particle system
The particle system is based on the ShaderParticleEngine, so modyfing it won't be that THREE.js'ish.
From what I can tell, the particle-system creates and stores SPE.Emitter's. You can access them here:
let particleSystem = this.el.component['particle-system']
let emitterGroup = particleSystem.particleGroup.emitters
Changing values - for example, if you have one emitter:
emitterGroup[0].disable() // disables the emitter
emitterGroup[0].opacity.value = 0.1 // sets the opacity
2. A job for a-frame custom components
I'd create a custom component - which will upon any change iterate through the emitter group and change the attributes:
AFRAME.registerComponent('particle-controller', {
schema: {
opacity: {default: 1}
},
init: function() {
let system = this.el.components['particle-system']
this.emitterGroup = system.particleGroup.emitters
},
update: function() {
this.setValueInEmitters(this.emitterGroup, 'opacity', this.data.opacity)
},
// may come in handy when changing more key-value pairs
setValueInEmitters(group, prop, value) {
for (let i = 0; i < group.length; i++) {
group[i].prop.value = value
}
}
})
The schema could be fed with key-value pairs or such to make the component more universal.
3. Why setAttribute is so heavy
Gathering from the source code, any change removes the old particle group and creates a new one from scratch.

How to delete all but most recent X children in a Firebase node?

Given a Firebase node lines filled with unique-ID children (from push() operations), such as this:
Firebase--
--lines
--K3qx02jslkdjNskjwLDK
--K3qx23jakjdz9Nlskjja
--K3qxRdXhUFmEJdifOdaj
--etc...
I want to be able to delete all children of lines except the most recently added 200 (or 100, or whatever). Basically this is a cleanup operation. Now I know I could do this by grabbing a snapshot of all children of lines on the client side, counting the entries, then using an endsAt(totalChildren-numToKeep) to grab the relevant data and run remove(). But I want to avoid grabbing all that data to the client.
Is there an alternative to my idea above?
Keep the most recent N items, is one of the trickier use-cases to implement. If you have any option to change it into "keep items from the past N hours", I recommend going that route.
The reason the use-case is tricky, is that you're counting items and Firebase does (intentionally) not have any count-based operations. Because of this, you will need to retrieve the first N items to know which item is N+1.
ref.child('lines').once('value', function(snapshot) {
if (snapshot.numChildren() > MAX_COUNT) {
var childCount = 0;
var updates = {};
snapshot.forEach(function (child) {
if (++childCount < snapshot.numChildren() - MAX_COUNT) {
updates[child.key()] = null;
}
});
ref.child('lines').update(updates);
}
});
A few things to note here:
this will download all lines
it performs a single update() call to remove the extraneous lines
One way to optimize this (aside from picking a different/time-based truncating strategy) is to keep a separate list of the "line ids".
lineids
--K3qx02jslkdjNskjwLDK
--K3qx23jakjdz9Nlskjja
--K3qxRdXhUFmEJdifOdaj
So you'll still keep the data for each line in lines, but also keep a list of just the ids. The code to then delete the extra ones then becomes:
ref.child('lineids').once('value', function(snapshot) {
if (snapshot.numChildren() > MAX_COUNT) {
var childCount = 0;
var updates = {};
snapshot.forEach(function (child) {
if (++childCount < snapshot.numChildren() - MAX_COUNT) {
updates['lineids/'+child.key()] = null;
updates['lines/'+child.key()] = null;
}
});
ref.update(updates);
}
});
This last snippet is slightly more involved, but prevents from having to download all lines data by just downloading the line ids.
There are many variations you can choose, but I hope this serves as enough inspiration to get started.

Best way to perform Update call when input field value is changed in Meteor JS?

Hypothetical example - you have an "Items" collection, where each item has a quantity and price that's stored in the db.
That quantity is an input field.
We want the database to be updated when the quantity is changed - with no "submit" button. There are multiple ways of going about this. Two examples:
Update db on "changed":
'change input.qty': function (evt) {
var qty = $(evt.target).val();
if (qty==null){
qty=0;
};
Items.update(this._id,{$set:{quantity: Number(qty)}});
},
Update db on "keyup":
'keyup input.qty': function (evt) {
var qty = $(evt.target).val();
if (qty==null){
qty=0;
};
Items.update(this._id,{$set:{quantity: Number(qty)}});
},
1 is more efficient - it only performs the update call once, after the user has clicked outside of the input box. However, it's a worse user experience, because the updates are not reflected on the page as they're typing. (For example, say the "price" field is calculated reactively based on your input quantity)
2 is a better user experience but can be extremely inefficient(ie typing in 103.58 makes FIVE database calls)
Are there better alternatives or a good middle ground?
That's the exact situation for which _.throttle method was created.
'keyup input.qty': _.throttle(function (evt) {
...
}, 350),
When you wrap your handler with _.throttle that way, it will be called only once per the given number of milliseconds, even if the input keep changing more frequently.
350 is a good value in most cases, though the exact optimum value may depend on the interface you're designing.
Plagiarising #Hubert OG's answer, except to recommend the "debounce" function instead, and the "input" & "change" events. (_.debounce works the same as the accepted solution from #Dave without the boilerplate.)
That's the exact situation for which _.debounce method was created.
'input input.qty, change input.qty': _.debounce(function (evt) {
...
}, 350),
When you wrap your handler with _.debounce that way, it will be called only once after all input has stopped (for at least a given number of msec)
Here's how I usually attack this problem:
var handle = null;
------------------------
'input input.qty': function (evt) {
var self = this;
if (handle)
clearTimeout(handle);
handle = setTimeout(function () {
var qty = $(evt.target).val();
if (qty==null){
qty=0;
};
Items.update(self._id,{$set:{quantity: Number(qty)}});
}, 500);
},
You can play with the number 500 a bit to get it to your liking. With this solution you'll only get a database call when the user has stopped typing for 500 milliseconds.
I'd switch to the input event as well, it takes care of cut, paste, and key entries.
Both seem like good solutions. The first is better in my opinion - keyup also registers things like pressing enter or other keys. Since I have multiple items with multiple quantity fields, here's what I ended up going with:
var handle = [];
'input input.qty': function (evt) {
var id = this._id;
if (handle[id]){
clearTimeout(handle[id]);
}
handle[id] = setTimeout(function () {
var qty = $(evt.target).val();
Items.update(id,{$set:{quantity: Number(qty)}});
}, 750);
}

Meteor minimongo dynamic cursor

In my client UI I have a form with differents search criterias, and I'd like to reactively update the results list. The search query is transformed into a classical minimongo selector, saved in a Session variable, and then I have observers to do things with the results:
// Think of a AirBnb-like application
// The session variable `search-query` is updated via a form
// example: Session.set('search-query', {price: {$lt: 100}});
Offers = new Meteor.Collection('offers');
Session.setDefault('search-query', {});
resultsCursor = Offers.find(Session.get('search-query'));
// I want to add and remove pins on a map
resultCursor.observe({
added: Map.addPin,
removed: Map.removePin
});
Deps.autorun(function() {
// I want to modify the cursor selector and keep the observers
// so that I would only have the diff between the old search and
// the new one
// This `modifySelector` method doesn't exist
resultsCursor.modifySelector(Session.get('search-query'));
});
How could I implement this modifySelector method on the cursor object?
Basically I think this method needs to update the compiled version of the cursor, ie the selector_f attribute, and then rerun observers (without losing the cache of the previous results). Or is there any better solution?
Edit: Some of you have misunderstood what I'm trying to do. Let me provide a complete example:
Offers = new Meteor.Collection('offers');
if (Meteor.isServer && Offers.find().count() === 0) {
for (var i = 1; i < 4; i++) {
// Inserting documents {price: 1}, {price: 2} and {price: 3}
Offers.insert({price:i})
}
}
if (Meteor.isClient) {
Session.setDefault('search-query', {price:1});
resultsCursor = Offers.find(Session.get('search-query'));
resultsCursor.observe({
added: function (doc) {
// First, this added observer is fired once with the document
// matching the default query {price: 1}
console.log('added:', doc);
}
});
setTimeout(function() {
console.log('new search query');
// Then one second later, I'd like to have my "added observer" fired
// twice with docs {price: 2} and {price: 3}.
Session.set('search-query', {});
}, 1000);
}
This doesn't solve the problem in the way you seem to be wanting to, but I think the result is still the same. If this is a solution you explicitly don't want, let me know and I can remove the answer. I just didn't want to put code in a comment.
Offers = new Meteor.Collection('offers');
Session.setDefault('search-query', {});
Template.map.pins = function() {
return Offers.find(Session.get('search-query'));
}
Template.map.placepins = function(pins) {
// use d3 or whatever to clear the map and then place all pins on the map
}
Assuming your template is something like this:
<template name="map">
{{placepins pins}}
</template>
One solution is to manually diff the old and the new cursors:
# Every time the query change, do a diff to add, move and remove pins on the screen
# Assuming that the pins order are always the same, this use a single loop of complexity
# o(n) rather than the naive loop in loop of complexity o(n^2)
Deps.autorun =>
old_pins = #pins
new_pins = []
position = 0
old_pin = undefined # This variable needs to be in the Deps.autorun scope
# This is a simple algo to implement a kind of "reactive cursor"
# Sorting is done on the server, it's important to keep the order
collection.find(Session.get('search-query'), sort: [['mark', 'desc']]).forEach (product) =>
if not old_pin?
old_pin = old_pins.shift()
while old_pin?.mark > product.mark
#removePin(old_pin)
old_pin = old_pins.shift()
if old_pin?._id == product._id
#movePin(old_pin, position++)
new_pins.push(old_pin)
old_pin = old_pins.shift()
else
newPin = #render(product, position++)
new_pins.push(newPin)
# Finish the job
if old_pin?
#removePin(old_pin)
for old_pin in old_pins
#removePin(old_pin)
#pins = new_pins
But it's a bit hacky and not so efficient. Moreover the diff logic is already implemented in minimongo so it's better to reuse it.
Perhaps an acceptable solution would be to keep track of old pins in a local collection? Something like this:
Session.setDefault('search-query', {});
var Offers = new Meteor.Collection('offers');
var OldOffers = new Meteor.Collection(null);
var addNewPin = function(offer) {
// Add a pin only if it's a new offer, and then mark it as an old offer
if (!OldOffers.findOne({_id: offer._id})) {
Map.addPin(offer);
OldOffers.insert(offer);
}
};
var removePinsExcept = function(ids) {
// Clean out the pins that no longer exist in the updated query,
// and remove them from the OldOffers collection
OldOffers.find({_id: {$nin: ids}}).forEach(function(offer) {
Map.removePin(offer);
OldOffers.remove({_id: offer._id});
});
};
Deps.autorun(function() {
var offers = Offers.find(Session.get('search-query'));
removePinsExcept(offers.map(function(offer) {
return offer._id;
}));
offers.observe({
added: addNewPin,
removed: Map.removePin
});
});
I'm not sure how much faster this is than your array answer, though I think it's much more readable. The thing you need to consider is whether diffing the results as the query changes is really much faster than removing all the pins and redrawing them each time. I would suspect that this might be a case of premature optimization. How often do you expect a user to change the search query, such that there will be a significant amount of overlap between the results of the old and new queries?
I have the same problem in my own hobby Meteor project.
There is filter session var where selector is storing. Triggering any checkbox or button changes filter and all UI rerender.
That solution have some cons and the main - you can't share app state with other users.
So i realized that better way is storing app state in URL.
May be it is also better in your case?
Clicking button now change URL and UI rendering based on it. I realize it with FlowRouter.
Helpful reading: Keeping App State on the URL

How can I update multiple collections in Meteor in the same transaction?

I have the following client-side code (with two collections A and B):
var id = A.insert({name: 'new element of A');
var b = B.findOne({name: 'parent of new element of A'});
B.update(b._id, { $set: { child: id } });
The above code works fine, i.e., the server side collections are correctly updated. The problem happens on the client where I have a template that reacts on both A and B collection changes.
The template reacts as follows:
It immediately redraws itself, based on the latency compensation mechanism, showing the correct changes for both collections
Soon after, it redraws itself again but without the changes (as if they had been rejected)
It does not refresh automatically anymore afterwards. But, if I hit the refresh button, the template redraws once more and now shows the correctly updated collections (both A and B)
On the second refresh only one of the collection (the parent B) has been updated and the template displays incoherent data (as if the updates had not happened).
I think this is because I am not dealing here with one single transaction that updates both collections at the same time, confusing the client side template.
How can I solve this?
EDIT:
I must add that in my case I have two complementing subscriptions to the child database:
var A = new Meteor.Collection('children');
handle1 = Meteor.subscribe('children1');
handle2 = Meteor.subscribe('children2');
and on the server
Meteor.publish('children1', function () {
return A.find({ sex: male });
}
Meteor.publish('children2', function () {
return A.find({ sex: female });
}
Could this be the reason for, when I insert a new element in A, I get the weird behavior described above?
it might work better if you do it all in one command
B.update({name: 'parent of new element of A'}, { $set: { child: A.insert({name: 'new element of A') } });
you'll have to put this in a method since you arent updating based on the id
you could also try to manually call Deps.flush() after the operations, that shouldnt be needed, but its something to try

Resources