i've been battling for a few days with an autorun function that renders elements on a canvas for two players in a board game. the whole process from when 1 player places a move until its visible for the other player took too long. i managed to cut alot by optimizing the code i have written as a meteor beginner, and also by placing the method that updates the game move inside a (meteor.isServer) so it won't first run the method in the move maker's client (it was already just inside the server's folder).
one delay in time which is beyond my understanding, happens from the moment the server finishes with the move making method until the client's autorun begin to run(about 60 ms delay for chrome, and 150(!) for firefox). i've ran some tests to try and narrow down the options for why it happens, the last one was to subscribe to the games collection from a different page, and see if the response time is slow there too. this is the code:
Template.Watch.onCreated(function(){
this.subscribe('activeGames');
this.autorun(()=>{
if(this.subscriptionsReady()){
console.log(Games.findOne().turn + " " + (new Date).getTime());
}
});
});
Template.Watch.helpers({
games: ()=> {
console.log(Games.findOne().turn + " " + (new Date).getTime());
return Games.find({result: false});
}
});
now, on the client both console.logs appeared whenever a move was played, the weird thing is that the helper's console.log was fired with no delay, 50 ms before the autorun's console.log which had the same delay as in the game page.
by my poor understanding, if the client knows a change was made to the database, the autorun should simply run, just like the helper simply runs.
i would be very grateful to whoever explains to me the behavior of the autorun that causes that delay, and if there is a way to cancel it.
Thanks in advance!
When you call a method on the client using Meteor.call, two things happen in parallel:
The client sends a request to the server to run the method in a secure
environment, just like an AJAX request would work A simulation of the
Method runs directly on the client to attempt to predict the outcome
of the server call using the available information.
So here is the reason why your Helper console print before the autorun.
Your mini-mongo(client side DB) get updated before the server data update according to point number 2.
According to point number 1, the client sends a request to the server, then server updates the server database. Server re-publish the data to the client and autorun reinitialize in your case.
So the point is your mini mongo(client side DB) updated before the subscription ready. that's why you got console log in helper before your autorun.
One more thing happen when your server data to client::
If the result from the server comes back and is consistent with the
simulation on the client, everything remains as is. If the result on
the server is different from the result of the simulation on the
client, the UI is patched to reflect the actual state of the server.
Related
Is it possible to stop (kill) asynchronous Call?
In my app I have at client side sth like:
Meteor.call('doCalculation', function(err, result) {
//do sth with result
});
'doCalculation' may take long time (this is ok) I dont want user to start new call when he/she has already one running call, I want to allow user to stop current call and submit new one. How correctly do this?
The only idea I have is to communicate between client and server using mongo. In some place in 'doCalculation' function I can observe some mongo document/collection and based on this do sth in the function (e.g. call exception). Do you have any better ideas?
You can use a semaphore for this purpose. When the semaphore is 1, requests are allowed to be sent. When the semaphore is 0, requests are not allowed to be sent. The semaphore should be 1 by default and just before you send the request, you need to set it to 0. When a response is successful, you set the semaphore back to 1.
As about the timeout: You could use a time out using setTimeout after sending the request, like this:
if (semaphore) {
var isTimedOut = false;
var isSuccess = false;
semaphore = 0; //No need to use var keyword, as this should be declared outside of this scope
Meteor.call('doCalculation', function(err, result) {
isSuccess = true;
//do sth with result
});
setTimeout(function() {
if (!isSuccess) {
isTimeout = true;
//do something else, to handle the time out state
}
}, 10000);
}
This is tricky, because you cannot generally set timeouts from the client's point of view. You don't need to, for a bunch of architectural reasons. The most important thing is that if you lose network connectivity or the server crashes (two cases timeouts are designed to manage), the client is aware immediately because it is disconnected. You can use Meteor.status().connected if this happens often.
It sounds like you're running a long calculation on the server. My suggestion is to return a calculationId immediately, and then update a collection with progress, e.g., CalculationProgresses.update(calculationId, {$set: {progress: currentProgress}}) as you calculate. Your UI can then update the progress reactively, in the most convenient way possible.
Note, that when you do run long calculations on the server, you need to occasionally "yield," giving the chance for other work to happen. Node, on which Meteor is based, is tricky for long calculations if you don't master this notion of yielding. In Meteor, you can yield easily by updating a collection (e.g., your progress collection). This will solve lots of problems you're probably experiencing as you write your application.
i think you need a server-side solution for this. if you go with a client-side solution, you don't handle 2 cases:
the user reloads their browser
the user uses 2 browsers
i would create these methods:
isCalculationActive() -- this checks if the user already has a calculation active. on the server, you can either keep that fact in memory or write it to the db. on the client, if this returns false, then you can proceed to call doCalculation(). if true, you can give the user a popup or alert or something to ask if they want to cancel and proceed.
doCalculation() -- this cancels any outstanding calculation by that user and starts a new one.
with these implemented, the user can reload their browser w/o affecting either the running calculation or correct behavior. and if they try a 2nd browser, everything should still work as expected.
if you want to give the user the option to simply stop the job and not start a new one, then you can simply create:
cancelCalculation() -- this cancels any outstanding calculation by that user.
After lots of reading, I'm starting to get a better handle on Meteor's publish/subscribe model. I've removed the autopublish training wheels from my first app and while I have most everything working, I am seeing one issue.
When the app first loads, my publish and subscribe hooks work great. I have a block of code that runs in a Tracker.autorun() block which makes the subscribe calls, I am able to sequentially wait for data from the server using ready() on my subscribe handles, etc.
One feature of my app is that it allows the user to insert new documents into a collection. More specifically, when the user performs a certain action, this triggers an insert. At that point, the client-side JS runs and the insert into MiniMongo completes. The reactive autorun block runs and the client can see the inserted documented. The client updates the DOM with the new inserted data and all is well.
Furthermore, when I peek into the server-side MongoDB, I see the inserted document which means the server-side JS is running fine as well.
Here's where it gets weird. The client-side autorun block runs a second time (I'm not sure why) and this time, the client no longer has the inserted item. When the DOM renders, the newly inserted item is now gone. If I reload the page, all is well again.
Has anyone seen this behavior before? I'm also noticing that the server-side publish call runs once on page load but then it doesn't run again after the insert. This seems wrong because how else will the client get the reconciled data from the server after the insertion (i.e. after Meteor's client-side latency compensation)?
The important functions (ComponentInstances is the collection that is bugging out):
Publish block:
Meteor.publish('allComponentInstances', function (documentId, screenIndex) {
console.log(`documentId: ${documentId} screenIndex: ${screenIndex}`)
const screens = Screens.find({ownerDocumentId: documentId})
const selectedScreen = screens.fetch()[screenIndex]
return ComponentInstances.find({_id: {$in: selectedScreen.allComponentInstanceIds}})
})
Subscription block in autorun:
// ... a bunch of irrelevant code above
const allComponentInstancesHandle = Meteor.subscribe('allComponentInstances', document._id, 0)
if (allComponentInstancesHandle.ready()) {
isReady = true
screens = Screens.find({ownerDocumentId: document._id}).fetch()
const componentInstanceObjects = ComponentInstances.find().fetch()
allComponentInstances = {}
componentInstanceObjects.map((componentInstance) => {
allComponentInstances[componentInstance._id] = componentInstance
})
}
This is most probably you're inserting documents from client side. And you have not set up your permission rules properly. When you remove autopublish and insecure from your app, you are not allowed to insert/update/remove documents into collection unless you have allow/deny rules set up in the server side.
Meteor has a great feature called latency compensation which tries emulate your db operations before it gets the actual write operation in the db. And when the server tries to write in the db, it looks for allow/deny rules.If the permission rules doesn't allow the db operation or Whatever the reason( either allow/deny or authentication) for not actually written in the db, then the server data gets synchronized with your client side db.
This is why i assume you are seeing your document being inserted for the first time and gets disappeared within a second.
check this section of meteor docs.
http://docs.meteor.com/#/full/allow
I ended up solving this a different way. The core issue, I believe, has nothing to do with accept/deny rules. In fact, their role is still hazy to me.
I realize now what I've been reading all along in the Meteor docs: the publish functions return cursors. If the cursor itself doesn't change (e.g. if you're passing specific keys you want to fetch), then it won't really work as a reactive data source in the sense that new documents in a collection will not make the data publish again. You are, after all, still requesting the same keys.
The way forward is to come up with a publish cursor that accurately reflects the reactive data you want to retrieve. This sounds abstract but in practice, it means make sure the cursor is general, not specific to the specific keys you are retrieving.
I am writing a Meteor app that takes in external data from a machine (think IoT) and displays lots of charts, graphs, etc. So far so good. There are various pages in the application (one per graph type so far). Now as the data is being fed in "real-time", there is a situation (normal) where the data "set" gets totally reset. I.e. all the data is no longer valid. When this happens, I want to redirect the user back to the "Home" page regardless of where they are (well, except the home page).
I am hoping to make this a "global" item, but also don't want too much overhead. I noticed that iron:router (that I am using) has an onData()method but that seems a bit -- high overhead -- since it's just one piece of data that indicates a reset.
Since each page is rather "independent" and an user can stay on a page for a long time (the graphs auto-update as the underlying data changes) , I'm not even sure iron:router is the best approach at all.
This is Meteor 1.0.X BTW.
Just looking for a clean "proper" Meteor way to handle this. I could put a check in the redisplay logic of each page, but would think a more abstracted (read: global) approach would be more long-term friendly (so if we add more pages of graphs it automatically still works) ..
Thanks!
This is a job for cursor.observeChanges http://docs.meteor.com/#/full/observe_changes
Set up a collection that servers as a "reset notification" that broadcasts to all users when a new notification is inserted.
On Client:
criteria = {someCriteria: true};
query = ResetNotificationCollection.find(criteria)
var handle = query.observeChanges({
added: function (id, user) {
Router.go('home');
}
});
Whenever a reset happens:
notification = { time: new Date(), whateverYouWantHere: 'useful info' }
ResetNotificationCollection.insert notification
On insert, all clients observing changes on the collection will respond to an efficient little DDP message.
In an application that allows realtime chat between clients, I aim to integrate functionality that allows to define messages that are delivered at future points in time.
In the following example, I am able to insert messages, which are inserted directly into the template. However, I would like to display only messages that have a time smaller or equal to the current time, but automatically display messages that have future time points as soon as the time is reached. For example, if I insert a message from the console, which should be displayed 30 seconds in the future by calling Meteor.call("createMessage", 30000, "hello in 30 seconds"), the message should be automatically displayed after 30 seconds.
I started restricting the query in the publish function to time: {'$lt': new Date()}. However, I have trouble in making this reactive. I unsuccessfully tried several combinations of Tracker.autorun and cursor.observe.
Can anybody give me a hint how I accomplish the desired reactivity within the following running example?
1) html file
<body>
{{> chat}}
</body>
<template name="chat">
{{#each chatMessages}}
{{time}} - {{message}} <br>
{{/each}}
</template>
2) js file
//server and client
Messages = new Mongo.Collection("messages"); //{time: Sun Nov 02 2014 22:17:32 GMT+0100 (CET), message: "hello"}
//server
if(Meteor.isServer){
Meteor.methods({
'createMessage': function(timeOffset, message){
Messages.insert({
time: new Date(new Date().getTime() + timeOffset),
message: message
});
}
});
Meteor.publish("messages", function(){
return Messages.find({
//time: {'$lt': new Date()}
});
});
}
//client
if (Meteor.isClient) {
Template.chat.helpers({
chatMessages: function(){
return Messages.find({});
}
});
Tracker.autorun(function (){
mySub = Meteor.subscribe('messages');
});
}
If Date() was a reactive datasource, it would work but it's not.
You can create a Timer at server side that will handle it. The best design I see id: pick the next message future date and set a Timer with the time difference and also get the next message time. Of course it's dependes on how your application works.
Read more about Timers in Meteor: https://docs.meteor.com/#/full/timers
Reactivity means that the view reflects the data sources used to make that view, updates when those sources change, and only then (as a rule of thumb).
Therefore, if we want to accomplish what is described using reactivity, we must introduce a reactive change when the message goes live (the outlined model does not have such a change).
Two ways to accomplish this that I can think of:
Add an 'isLive' field to the message, and have the server change it at the right time, using timed callbacks and Meteor.startup (to avoid losing messages in case of a reboot). Somewhat complex, but clean and performant (when properly implemented).
Add a currentDate Session variable and use Meteor.setInterval etc. on the client, to keep it as current as you need (Because Session variables are reactive).
In the second alternative, the user could just change their system clock or use the javascript console to access future messages. Also, reactive events with a set interval seem rather contrived, unless that interval is significant to the problem domain itself (like a calendar app changing the 'today' session variable used to draw the red circle, every midnight).
A simpler (better?) non-reactive solution might be to simply render the future messages as hidden elements, and use javascript timers to show them at the right time. But it all depends on what you are dealing with of course.
I have an EC2 instance running a small node script connecting to Firebase. Strangely enough, it happens quite often on a small instance that the set operation gets executed immeditely but the callback function only gets called much later (between 30s to 2 minutes). Do you see any reason why it would happen that way?
console.log('creating');
// Create workspace
rootRef.child('spaces').child(chid).set(req.space, function(error) {
var end = new Date().getTime();
var time = end - start;
console.log('- created', error, time);
});
The bug is directly related to node 0.11 (set() callback is only called the first name in my scenario). Just revert to 0.10.x and it's all fixed!
I've been facing the same issue. the "Set" callback is not being invoked at all. I noticed, however, that if I run a snippet code similar to yours in a standalone file, the callback is invoked very quickly.
It turned out that if you're installing listeners on the same Node you're calling the "set" function on (i.e., on('child_added'), on('child_removed') ... etc) and that Node has a huge number of records, it'll simply take ages.
I removed the listeners ( to test) and the "set" started to invoke the callback very quickly.
I hope this helps!