Cocos2d-html5 - Restarting/quitting engine possible? - javascript

With Cocos2d-html5, is there any way to “quit” the engine and re-initialize its state so you can later restart the engine. We are using a Cocos2d game in a single page web app. If the user navigates to another page, we want to programmatically remove the canvas div and attempt to exit the Cocos2d engine. Unfortunately, it seems there’s no good way to do this and attempting to load a new game causes errors.
How can you cleanly unload a scene and quit the engine?
We are using V3.1 and tried several approaches, none of them seem to work. For example:
Trying to reset many variables that are held in the cc object (detailed below). This approach forces you to reset variables that look private as they start with _ and are not in the documentation.
picking, choosing and rewriting some of CCBoot.js but this appeared complicated and not sustainable for engine updates as so much of it is depended upon throughout the library.
Other approaches I thought of but all sound like a hack:
3. Null the whole cc object and somehow run the script again but that might mean stripping out a script tag or module and adding and running it again.
4. Wrap the cc object in another object so it is easier to reset. CCBoot.js looks like it might attach some things to the window object.
I have got furthest with the first approach but am stuck with context issues. When leaving the canvas, before I remove it from the DOM I call these:
// Remove anything that might possibly keep a reference to the old context
cc.textureCache.removeAllTextures();
cc._drawingUtil = null;
cc.stencilBits = null;
// Purge the cc.director, schedules, event listeners, running scene, animations, cached data.
cc.director.purgeDirector();
// Remove references to the context
cc._renderContext = null;
cc.webglContext = null;
window.gl = null;
cc._mainRenderContextBackup = null;
// Remove reference to DOM elements
cc._canvas = null;
cc.container = null;
cc._gameDiv = null;
// Reset CCBoot variables that might stop us from re-initialising
cc._rendererInitialized = false;
cc._setupCalled = false;
cc._renderType = -1;
Then when we restart on the second or subsequent time call
// Reset all system and flag variables
cc._initSys(cc.game.config, cc.game.CONFIG_KEY);
cc.game.run();
And here are the kind of errors I get. It looks like it's not properly resetting the context:
WebGL: INVALID_OPERATION: bindTexture: object not from this context
WebGL: INVALID_OPERATION: texImage2D: no texture
WebGL: INVALID_OPERATION: uniformMatrix4fv: location is not from current program
WebGL: INVALID_OPERATION: vertexAttribPointer: no bound ARRAY_BUFFER

I've managed to get it stopping/resuming enough for a specific use case now, here is a demo:
http://plnkr.co/edit/BQmmHi?p=preview
There is now a public way to pause a game in v3.13, so I can call:
cc.game.pause();
followed by removing the cc.container from the DOM:
cc.container.parentNode.removeChild(cc.container);
Then I can place it back in the DOM again anywhere I would like:
targetContainerEl.appendChild(cc.container);
Then resume the game by calling:
cc.game.resume();
Note: The next line was was needed after replacing in the DOM in a complex app in cocos2d v3.5, but not sure if it's needed in v3.13 any more:
cc.EGLView._instance._frame = cc.container.parentNode;
The demo from the link above is being removed and placed in 2 different DOM elements and pausing/resuming.

Related

JavaScript - do local class variables get garbage collected?

My question is similar to Do local variables inside of a loop get garbage collected?
I have a class that uses recursion to play a blip sound effect in a typewriter style character dialogue screen:
class DialogueBox extends Component{
constructor(props){
super(props)
this.state = {...
}
...
}
typeWriter(txt,i,speed=50) {
if(i==txt.length){
...
return
}
else{
// a blip sound effect plays on every new character typed
let sfx = new Audio(speechBlip);
sfx.play();
...
setTimeout(()=>this.typeWriter(txt,i+1,speed),speed);
}
}
Note the local variable let sfx = new Audio(speechBlip) that is being instantiated multiple times. Will this result in a ton of Audio objects stored in memory that will never be cleaned up?
I'm using this method because I like how it sounds more than creating one Audio() in the constructor and re-setting it to 0 time or only replaying when the file completes playing.
Would this method be a major drag on memory? I tried using the memory panel of dev tools but I am not sure if I am interpreting it correctly and am unsure about how it will scale...
TL;DR; yes it will be cleared up
Answer:
There is a chance it might not be cleared. if you ever store that sfx variable for later use ( lets say you add to some form of event, timeout, etc... ) then it will be cleared after that listener is executed.
This is a very very situational problem tho, keep that in mind! If for example you have an event emitter and you attach a function to an on event, then that function will not be cleared from memory ( for example ).
Anyway. If that variable is used to just do sfx.play(), then it will be cleared from memory.
A small suggestion tho. Why don't you create a class variable lets say: this.blipSound = new Audio(), and use this.blipSound.play() where you need it, you don't have to set it to null or 0 every time as you suggested, just keep it? That way you won't have to worry about memory leaks, as there will be one instance of the class?

How run javascript script immediately after dom tree is loaded to avoid the user from seeing the "single buffering" effect?

Premise: I need to apply changes trough JavaScript because the free platform on which my site is based does not allow changes to some parts of the template, so I can't insert the scripts in the source through the tag, but I can insert them in the script administration panel provided by the platform.
The script put in the panel are normally called up before the building of the DOM tree, so I have to insert the scripts in the $(function) to force the call at the end of the dom tree building, but in this way the function is called after the page has been drawn and the user first sees the old page and then the new page generating a "single buffering" effect.
Summarize:
1 - browser loading page and display it
2 - browser loading image and display it
3 - browser execute script and display changes
Quite often the point two take about 1 seconds or more so the old page is displayed for that time.
Can I force javascript to run code between point one and point two, so that point three become point two, and point two become point three?
I try:
$(function) {}
$(window).onload() {}
$(document).ready() {}
document.addEventListener('DOMContentLoaded', function() {});
Nothing of these works as I want.
Thank you in advance for help.
Solution found:
// add 'timer' function which will be called once every millisecond
var step = setInterval(step, 1);
function step() {
// verify if element is loaded
if (document.getElementById('*element-id*')) {
// add code here
// stop function call
window.clearInterval(step);
}
}
This work very well for me.

Automation script is not working?

This is the first time I get my hands on with automation instruments in xcode The script works well for all button taps but the one making server connection. I don't know the reason
Here is the script I tried so far
var target = UIATarget.localTarget();
target.pushTimeout(4);
target.popTimeout();
var window=target.frontMostApp().mainWindow()
var appScroll=window.scrollViews()[0];
appScroll.logElementTree();
UIATarget.localTarget().delay(2);
appScroll.buttons()[1].tap();
The above script works up to showing the UIActivityIndicator instead of moving to next controller after success
I know There must be a very simple point I am missing. So help me out
UIAutomation attempts to make things "easy" for the developer, but in doing so it can make things very confusing. It sounds like you're getting a reference to window, waiting for a button to appear, then executing .tap() on that button.
I see that you've already considered messing with target.pushTimeout(), which is related to your issue. The timeout system lets you do something that would be impossible in any sane system: get a reference to an element before it exists. I suspect that behind-the-scenes, UIAutomation repeatedly attempts to get the reference you want -- as long as the timeout will allow.
So, in the example you've posted, it's possible for this "feature" to actually hurt you.
var window=target.frontMostApp().mainWindow()
var appScroll=window.scrollViews()[0];
UIATarget.localTarget().delay(2);
appScroll.buttons()[1].tap();
What if the view changes during the 2-second delay? Your reference to target.frontMostApp().mainWindow.scrollViews()[0] may be invalid, or it may not point to the object you think you're pointing at.
We got around this in our Illuminator framework by forgetting about the timeout system altogether, and just manually re-evaluating a given reference until it actually returns something. We called it waitForChildExistence, but the functionality is basically as follows:
var myTimeout = 3; // how long we want to wait
// this function selects an element
// relative to a parent element (target) that we will pass in
var selectorFn = function (myTarget) {
var ret = myTarget.frontMostApp().mainWindow.scrollViews()[0];
// assert that ret exists, is visible, etc
return ret;
}
// re-evaluate our selector until we get something
var element = null;
var later = get_current_time() + myTimeout;
while (element === null && get_current_time() < later) {
try {
element = selectorFn(target);
} catch (e) {
// must not have worked
}
}
// check whether element is still null
// do something with element
For cases where there is a temporary progress dialog, this code will simply wait for it to disappear before successfully returning the element you want.

Simple function using a lot of memory

I'm using local storage because after the click, the page reloads, so I can keep track of the last item clicked.
As you can see, I've tried to clear localStorage in order to shrink the memory in use, but it's already at 1.000.000K in less then 10 minutes of usage.
Is this script redeclaring this variables at different location everytime my page reloads?
What is happening that is making it use so mant memory?
This is my entire code.
It's an extension I'm creating for chrome, it selects an option and clicks the button, the button submits a form, the page reload, and it does eveything again and again.
var last = localStorage.getItem('last');
var current = getNext(last);
var prox = getNext(current);
localStorage.clear();
$('select[name="myselect"] option').each(function(){
if($(this).val().indexOf(current)>-1){
$(this).prop('selected', true);
$('.abc').first().click();
localStorage.setItem('last',current);
}
});
function getNext(current){
var arrIds = ['227','228','229','230','231','232'];
return arrIds[arrIds.indexOf(current)+1] || '227';
}
Updated code, without var declarations, that has decreased memory consumption drastically, but with time, the memory raises (in ten minutes went from 160.000K to 240.000K):
$('select[name="myselect"] option').each(function(){
if($(this).val().indexOf(getNext(localStorage.getItem('last')))>-1){
$(this).prop('selected', true);
$('.abc').first().click();
localStorage.setItem('last',getNext(localStorage.getItem('last')));
}
});
function getNext(current){
var arrIds = ['227','228','229','230','231','232'];
return arrIds[arrIds.indexOf(current)+1] || '227';
}
As per the discussion in the comments below the question, the issue appears to come from jQuery itself. The exact reason isn't known at this point, but it appears jQuery has retained data that is not being released when the form is submitted.
This probably has to do in part with some aspect of it being a Chrome extension, since typically on a refresh, the browser would release all memory including globals.
jQuery creates great potential for memory leaks by holding data and closures in a global reference called jQuery.cache. If this is not cleaned up properly, leaks abound.
Since you're creating a Chrome extension, there shouldn't be much to compel you to use jQuery, since you'll not need to worry about browser incompatibility from browsers like IE6 and 7. By using the DOM API directly without such dependencies, the overall code will be smaller and much faster.

Parsing HTML DOM in web worker

I am trying to extract content from web pages using my firefox/chrome/safari extension. Capturing works fine but when I capture full web pages, it takes a long time and UI gets blocked. I want to move the capture/DOM parsing code to a different thread (Web Worker). But web workers do not have access to the DOM. Is there a way I can work around this?
I am using the following code to inject the script into the web page:
function executeScript(script, messageKey, callback) {
var wm = Components.classes["#mozilla.org/appshell/window-mediator;1"].getService(Components.interfaces.nsIWindowMediator);
var mainWindow = wm.getMostRecentWindow("navigator:browser");
mainWindow.gBrowser.selectedBrowser.messageManager.loadFrameScript(script, true);
mainWindow.gBrowser.selectedBrowser.messageManager.addMessageListener(messageKey, callback);
}
executeScript("chrome://extension/content/contentscript.js", "onSelectionReceived", onSelection);
All the DOM processing is happening inside this script 'contentscript.js'
If the work you are trying to perform needs to interact with the DOM and it happens to take a long time, and you can't refactor it to not need to interact with the DOM, then there is a way without using WebWorkers.
(Because as you discovered, WebWorkers do not have access to the DOM)
Consider using Array Processing. The basic idea is to split up the work you need to do into different chunks, and after reach chunk of work is completed, periodically give back control to the DOM (UI Thread) using the Timer.
Here is a basic example of Array Processing:
function saveDocument(id){
var tasks = [openDocument,writeText,closeDocument,updateUI]
setTimeout(function(){
//execute the next task
var task = tasks.shift();
task(id);
//determine if there's more
if (tasks.length > 0) {
setTimeout(arguments.calee, 25);
}
}, 25);
}

Categories