Friday, May 8, 2020

Angular, Starting Out, Weird Performance Issues

I've been a professional software developer/engineer/programmer/whatever-role-title for 11 1/2 years. The types of projects I've worked on professionally have weaved in and out of academia, defense, enterprise, web, and traditional/desktop applications (in all combinations). My attitude and relationship with web development has been pretty complicated historically-speaking, so much that I scrapped several drafts of this post because they became too ranty.

Long story short: I decided to go with Angular, TypeScript, and Electron as the core technologies used in Integral. Why target the browser for what's intended to eventually be a professional game engine? Well, basically every device has a WebGL-capable and controller-ready browser built in. There's no cross-compiling necessary, and WebGL standardizes the interfacing for testing and using extended OpenGL features. Electron wraps it all up as a "native" application, including native functionality like actual file system IO (via IPC--or interprocess communication--APIs). One of my goals for Integral all along has been to be highly cross-platform, going as far as editing content on my phone and being able to play my games on any console. (As a matter of fact, I did a little bit of development for my first browser-based attempt on my phone using Termux (a Linux emulator) and a BlueTooth keyboard.) It honestly just makes sense, and it helps that I really dove in to Angular for my day job in the past year.

Even though I'm just a one-person team, I've been trying to take an agile-esque approach by breaking work down into proper user story-style tickets. I've found that using an actual ticketing system helps with the mental processing here, versus past attempts using large bullet lists in a Google Doc (or the like). As projects of this scale go, there have also been plenty of hurdles at every step, and I've been making sure to refactor and address other technical debt often. These things slow implementing features, but establish and maintain a cohesiveness to the entire codebase.

Recently, I've been working my way passed the "Hello World Triangle" phase into actually interacting with the triangle. The abstraction I've come up for a unified editing/selection model is: Handles represent different objects (i.e. entire triangles, entire edges, individual vertices, entire entities, etc.), and different editing and selection modes yield different handles. That way, the input system can interact with handles in (here I say it again) a unified way.

I haven't posted about how the world is organized, how the renderer works, or any other "dry" details because they're not finalized yet. I think I have them solid for the time being, but not to the level that I'd feel comfortable blogging about them. So let's just start from: Hey, I finally have some (selection) handles for the three vertices of my one glorious triangle:
(To toot my own horn a little bit, this screenshot shows functional persistence, input/camera-move, texture management, and rendering systems. The wireframes are can be toggled off entirely or selected between exterior edges (silhouettes), interior edges, or both. The "shaded" geometry can also be toggled on and off, though right now everything is Flat Full-Bright or off.)

When I finally implemented my handle highlighting logic (complete with that, "Ah yes, this is it!" feeling) and switched windows to test it, I noticed that the handle wouldn't really change colors until the mouse stopped moving while hovering over it:
Long story short, this problem about killed me (or less dramatically, short-circuited the project). I added in log statements everywhere, measuring the start-to-finish milliseconds for the render methods, mouse event methods, mouse-in-handle collision detection, etc. I saw everything happening in perfect sequence: event fires at x millis, handle collision detection at x+1 millis, render at x+2 millis. I even added checks to the render methods to print special logs if the object's color matched the highlight handle color, and those occurred far before I would actually see the change. I was desperate, until I ran the Chrome (Electron) profiler and found zone-evergreen consuming up to 87% of the cpu cycles.

zone-evergreen... isn't that... Angular! Zone, as far as Angular and most people are concerned, is the thing that triggers change detections so the backing data models and HTML components can automagically stay in sync. Of course this thing would be a hog, and my research told me it was running every single mouse event!

I toyed around with different ways to optimize Zone for my purposes. Nothing really worked in those regards. I then tried just disabling Zone (and thus change detection) all together, and my first test afterwards was the biggest sigh of relief I've had for this project--it finally worked. My beautiful handles highlighted green with mouse hover, and then went back to purple again. But oops, change detection was now off, so I dug further by leaving it on and removing one layer of components at a time. As it turns out, it was actually something in the Texture Manager (on the left, where you can see the crude "Beautiful Chair" image)--I suspect because are also components in it that listen for mouse events.

What I ended up doing was removing the Texture Manager components from the change detection hierarchy and triggering it manually when specific events occur. It works like butter.

No comments:

Post a Comment