ondras / rot.js

ROguelike Toolkit in JavaScript. Cool dungeon-related stuff, interactive manual, documentation, tests!
https://ondras.github.io/rot.js/hp/
BSD 3-Clause "New" or "Revised" License
2.33k stars 254 forks source link

Importing `ROT` breaks Phaser.js's game loop #131

Closed nemoDreamer closed 6 years ago

nemoDreamer commented 6 years ago

I'm trying to use ROT's FOV and Map generation utils inside of a Phaser game. I've got a webpack / babel setup, and am able to do

import ROT from `rot-js`;

and even

import { FOV } from 'rot-js';

and can get access to all the FOV.RecursiveShadowcasting goodness, etc.

But then Phaser's normal init-preload-create-update-render-update-render-... loop is broken. Even just importing it without using it in the game code. None of the code seems to error, and when I log all my methods to console, I just get:

init start
init end
preload start
preload end
create start
create end
render start
render end
render start
render end
render start
render end

Missing are the update calls (before the render calls), which are where all the game logic and stage preparation happens 😆

Is there any ROT code that automatically executes on import, and might be setting some global classes / behaviors that interfere with Phaser?


I've currently extracted FOV out of ROT into a static file, and that works great, but I'd love to keep ROT as a real dependency and use much more of it 😭

I'd love to just do:

import { FOV, Map } from 'rot-js';

class Game {
    create() {
        this.tilemap = this.add.tilemap('Tilemap');
        this.tilemap.addTilesetImage('tileset_11x11', 'Tileset');
        this.groundLayer = this.tilemap.createLayer('Ground');
        this.fovLayer = this.tilemap.createLayer('Fog');

        this.FOV = new FOV.RecursiveShadowcasting((x, y) => {
            const tile = this.tilemap.getTile(x, y, this.groundLayer);
            return !tile || !tile.properties.collides;
        });

        // ...
    }

    update() {
        // ...

        if (moved) {
            this.FOV.compute(this.player.x, this.player.y, 10, (x, y, r) => {
                const tile = this.tilemap.getTile(x, y, this.fovLayer);
                if (tile) {
                    tile.alpha = r * 0.1;
                }
            });
        }
    }
}
ondras commented 6 years ago

Hi @nemoDreamer,

thanks for your bugreport! Let me reply to individual parts of your post.

and even

import { FOV } from 'rot-js';

and can get access to all the FOV.RecursiveShadowcasting goodness, etc.

Nice! Would you care to share the webpack setup that allows for this? I am considering refactoring rot.js to ES6 modules, but my time is very limited and such endeavor might be very complex.

Even just importing it without using it in the game code.

Good, so you are probably able to create a nicely small reduced test case? That would be most helpful for debugging the issue.

Is there any ROT code that automatically executes on import, and might be setting some global classes / behaviors that interfere with Phaser?

Yes, there is some setup code. But:

I've currently extracted FOV out of ROT into a static file, and that works great

so this looks like the problem is not related to rot.js's setup, as that would not work also with the static file scenario.

Are you able to produce a URL/Gist that showcases the problem? Just Phaser (ideally non-minified), your rot.js build and a minimal app code that logs those most important lifecycle phases.

nemoDreamer commented 6 years ago

Cool, will do the Gist! (this is a side-project for me, so it'll be slower, sorry...!)

Answers:

import { FOV } from 'rot-js'; Would you care to share the webpack setup that allows for this?

Nothing at all, just works! Due to the nature of webpack imports, it's probably still bundling the whole thing anyway and then just doing a const FOV = ROT.FOV...

With the "extracted FOV out of ROT into a static file" bit, I meant that I'd simply copied ROT.DIRS, ROT.FOV and ROT.FOV.RecursiveShadowcasting to a separate file and refactored slightly to make them stand-alone. Not an ideal solution if I also want to take advantage of updates and new algos... 😄

ondras commented 6 years ago

With the "extracted FOV out of ROT into a static file" bit, I meant that I'd simply copied ROT.DIRS,

Damn, so you truly removed the rot.js initialization phase, which is now the one to blame. Well, let's see the Gist.

nemoDreamer commented 6 years ago

Here goes! https://github.com/nemoDreamer/webpack-babel-phaser-rot-js

This evening, I'll try importing rot.js like I imported Phaser+Pixi (using the expose-loader) plugin which'll just expose ROT as an impartable variable.

nemoDreamer commented 6 years ago

Oooh: could it be related to rot.js' polyfill of requestAnimationFrame?

Ok, I really need to get back to work...!!! 😬

ondras commented 6 years ago

I am pretty certain I found out the cause.

TL;DR: the rot.js's requestAnimationFrame polyfill for nodejs environment does not pass the timestamp argument: https://github.com/ondras/rot.js/blob/master/node/node-shim.js#L4

Long story: rot.js contains polyfills for certain features that were not always available in older browsers. One of these is the requestAnimationFrame function, crucial for proper timing of animations. The relevant code works by checking the presence of said function and utilizing own implementation only when necessary: https://github.com/ondras/rot.js/blob/master/src/js/raf.js#L2

Unfortunately, there is also a second polyfill for requestAnimationFrame that is part of a larger set of nodejs-specific shims. This code does no feature testing, as nodejs runtime environment is almost guaranteed to lack these functions. By using a NPM version of rot.js, you use the nodejs-based build which forces the own (incorrect) implementation of requestAnimationFrame.

There are two distinct solutions:

  1. use a build of rot.js that is aimed at browsers. I have no idea how well this plays with NPM/Webpack. Probably not.
  2. fix both requestAnimationFrame polyfills so they pass the correct argument.
ondras commented 6 years ago

(Why is the polyfill incorrect? Because rot.js is old and older versions of requestAnimationFrame were not passing any arguments at all. See https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame for reference.)

nemoDreamer commented 6 years ago

Ah, so it was requetAnimationFrame. But I'd wondered, since raf.js seemed guarded against overwriting...

Are the node.js-specific shims under your control?

ondras commented 6 years ago

Are the node.js-specific shims under your control?

Yes, they are part of the rot.js codebase. They are not my code, but regular part of the repository. And I will fix them, as well as the browser-side polyfill (that gets obviously never used these days, but...).

ondras commented 6 years ago

@nemoDreamer would you mind updating rot.js (from NPM or Github) and trying again? I hope I fixed the issue, but I have not tested that at all :-)

nemoDreamer commented 6 years ago

🎉 Woohoo! 🌮 🍕 ❗️

Thanks SO MUCH, @ondras! Now I can take this evening's tinkering to the next level!