-
-
Notifications
You must be signed in to change notification settings - Fork 3.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Using a separate math library for handling p5.js's math operations #6527
Comments
Maybe we could try using this or something similar: https://mathjs.org/ |
@RandomGamingDev thanks for starting the discussion.
I recently read the source code of
I wrote a simple sketch with Math.js and found it approachable. Skimming some issues, it doesn't look like Math.js uses hardware acceleration. They did mention stdlib's ndarray as a possible route forward. You'd have to ask @davepagurek how either approach might benefit internal calculations for WebGL. As for users, I saw you mentioned #5210 in Discord. @golanlevin made a good suggestion to start with an addon library and examples. He also mentioned my library número. It might provide some inspiration for how to make matrices play nicely with Personally, I'd study how three.js approaches the math section of their API for enhancements that could increase access. I used their Raycaster in a recent project and it's great. The implementation depends on their Ray object. @limzykenneth, @ericnlchen, @ChihYungChang, @bsubbaraman, @albertomancia, @JazerUCSB, @tedkmburu, @perminder-17, @Obi-Engine10, @jeanetteandrews, and @GregStanton may have some thoughts here, too. |
While I agree that most of the library's fine there are major parts that I do believe are cluttered and could use some improvement. p5.Vector.js is hard to read and has a lot of points that could really use improvement in how it's coded, and isn't expandable or collapsible to smaller or larger vectors. This leads to annoying to deal with vectors, and a repetitive codebase with code segments like this. Then there's also p5.Matrix.js which for some reason isn't in the While I could see the system getting polished and fixed to remove this clutter, better organize everything, make everything more expandable, better document things, and add more math functions, it won't be very easy to maintain because of p5.js's size, and the fact that p5.js isn't a heavily math oriented library. I feel that the best solution, at least at the current moment, would be to replace most of the internals of p5.js's math that have been causing this, and to just add a light wrapper around a more advanced, reliable, better documented, and more powerful library, rather than reinventing the wheel.
No, they don't use hardware acceleration, but I'm not really sure p5.js needs hardware acceleration rn, and I thought that recommending math.js could make for a good starting point.
Yes, it can be decently approachable, but it doesn't integrate very well with p5.js if you're trying to do some manipulation of p5.js's internal systems while relying on another math library (especially for more complex things like 4d scenes) and is even harder for beginners to use, understand, and learn from. I think p5.js should prioritize making its math easy to use and powerful from from the very beginning instead of relying on outside libraries or plugins, especially since this would help speed up the development of p5.js, bug testing, feature additions & enhancement, as well as make it easier for people who are new to certain topics understand them better, since documentation would likely be better with a math dedicated library. |
Just to chime in about the additional WebGL-specific aspects of the p5.Matrix class: In addition to everything else you all have mentioned, it needs to store its data in a format that we can quickly drop into a
It looks like Math.js doesn't do typed arrays just yet? We might need to either benchmark how much of a slowdown it is to convert, or patch in typed array support (it seems like one commenter in that discussion has successfully patched in support for it for their own uses.) In the past I've also used the glMatrix library, which is sort of on the other end of the spectrum, aiming to be an optimized library with WebGL compatibility. Another option could be to use something like that under the hood and add a p5 style API on top of it for easier use. @nickmcintyre from making numero, maybe you have some more knowledge about its Tensorflow backend and whether that fits the format? |
Converting between formats for the small matrices used by p5.js's shaders shouldn't be too large of a problem altho yeah, using another library for performance could be good too if we want to go down the performance route, altho I don't feel like that's p5.js's main goal and that choosing an easier to use library would be better for contributors and users of the library. |
@davepagurek here's a quick sketch with a simple example that creates a couple of @RandomGamingDev just my two cents, but I think your critique would be more helpful if you identified specific problems with the codebase. Implementing a bunch of vector/matrix operations from scratch in JavaScript is a complex job. Style is subjective, but I think the contributors to A few high-level questions come to mind: Are there specific performance issues that a third-party library would solve? Do people working on p5's core feel like any math is "missing"? Are there potential core features that are only feasible with a more powerful toolkit? |
Thanks for tagging me @nickmcintyre. I think it would help if we could answer the questions @nickmcintyre posed in terms of concrete user stories, or "contributor stories" in the case of the codebase. For now, I'll share my current thinking, although I'm not really sure about any of this! BackgroundFor some of my p5 sketches, I've written numerical integrators, I've used math.js to parse algebraic expressions, and I've used a different outside library for nonlinear regression:
These types of sketches require a lot of foundational code (e.g. for a coordinate system that conforms to mathematical conventions, for axes, for drawing arrows, etc.). So, I recently started a p5 add-on library called Mathemagical.js. It will likely contain a lot of the computational power under discussion here. Scope"Any math that's more complex" (to quote @RandomGamingDev) is an immense scope. Exposing this kind of functionality to the p5 user would mean a big addition to the API, so it should have a compelling justification. Once something goes into the API, we're basically stuck with it. Adding something the size of the math.js function reference would probably double the size of the API, at least. Most of it will be incomprehensible to beginners who are interested in creative coding, rather than math, and they may feel intimidated. Dependency issuesRegarding additional math features, @RandomGamingDev noted that "it won't be very easy to maintain because of p5.js's size, and the fact that p5.js isn't a heavily math oriented library." The argument, as I understood it, is that p5.js should therefore trust another project to develop and maintain the math functionality. Trusting another project with a large portion of the p5 API seems risky, especially right now. As far as I know, the project lead of math.js works on it in his spare time and there's not a larger organization backing it, in case he has to step out. More generally, WebGPU seems likely to shake up the technical computing landscape. Wrapping a dependency using the facade pattern or adapter pattern may mitigate the risk somewhat, but we may still end up being tied to the structure of whatever dependency we add. Internal improvementsAnother option is to depend on, and expose, only a small subset of features from a library like math.js. The only obvious way to narrow it down is to consider the math p5 already uses, e.g. p5.Matrix. For that, it probably makes sense to borrow from a library that's more specifically geared toward the purposes of p5, such as glMatrix or the three.js math folder, which others have mentioned. Maybe those libraries could inspire performance improvements or refactoring. Here are a few other projects that may help if p5.Matrix needs improvement (some have already been mentioned): Add-on libraryIt probably makes sense to relegate any large addition of math features to an add-on library like Mathemagical.js or número. For example, this may include numerical analysis, linear algebra, geometry, optimization, probability, and statistics. Since these features would be core to the purpose of the project, they'll attract users rather than intimidate them. Those same users can become contributors, so developing or maintaining extra math features will be less of a stretch. Math visualizations are another consideration. It seems likely that users of a graphics library with advanced math features will want to make math visualizations. But in p5, even something as simple as axes with tick marks feels out of scope. If p5 goes beyond graphical primitives and starts providing domain-specific visualizations (math or otherwise), the scope becomes unbounded. (Composite objects like axes also work better with an object-based interface, rather than a function-based interface like p5's. In Mathemagical, we provide both types of interfaces. Matplotlib is an example of a library from the Python ecosystem that does the same thing. The complexity is forced by the use case, but p5 works without this.) Edit: Made reply more concise, fixed a link. |
I did identify specific problems in #6527 (comment) and in the post itself, where I detailed the problems with the current math system. While I agree that the job is complex and that a lot of the job is done quite solidly, I don't believe that all of it's solid, or that a lot of the things added are a matter of just style.
This type of code is not only clearly apparent, and clearly full of bad practices rather than just being a certain style, but it's also spreading through p5.js and will likely continue to spread unless we clean up the code somehow, which is why I'm proposing that we do so by replacing the math library with a library with contributors that can add extra features in a clean and simple to use API for p5.js that can then be wrapped around with a custom p5.js API so that p5.js can safely swap between libraries and make the features of the underlying math library easily accessible to people who want to use them without having to add addons that quite likely won't work very well with p5.js's internals
I probably should've clarified, but by optimization I mainly meant optimization of the coding experience and workflow as well as optimization in terms of performance since I don't believe that performance is currently p5.js's main concern. However, I believe that there will be a significant performance increase from using a system that doesn't have the same problems as shown before with the p5.js codes and things like tons of large type checks and other checks. While I don't believe any truly core math is missing, I believe that we should make and effort to expand past that and provide the best experience possible instead of just the core because the core of p5.js is making things beginner friendly and easily accessible, which means that we should be making these more accessible. For instance, differently sized matrices and better more intuitive matrix types and operations (there are a lot of functions that apply only to
First off, I believe that you took my quote out of context since the full quote's "Plus, it's hard to integrate p5.js with other math libraries when you need any math that's more complex, for instance if someone wanted to do something that involved linear algebra, or calculus that isn't included in p5.js, like 4d scenes." Also, I wasn't saying to add every other bit of math that could possibly be needed to the p5.js's math API. To be more specific, I think that we should have a system where p5.js's current wrapper is kept (like what you're suggesting), but the internals are replaced with a better math library in a way that makes it a lot easier to switch between math libraries if needed, and that makes it a lot easier to expand by wrapping over the math library's function's with p5.js style coding. Also, even if we were to expand the API to wrap over the entire math library, I think that those who are interested in creative coding would either find it interesting, useful, encouragement to use this library over others that don't provide these things, or simply skip over them rather than being scared away from the intuitive API of p5.js by a few math functions grouped under a math part of the library. Plus, I feel that adding more math functions to the API to make it easier for people fits perfectly with p5.js's goals for ease of use, especially for beginners.
Yes, the argument is that p5.js should trust another project's math functionality and use it within itself.
Wrapping it should mitigate the risk enough for it to be worthwhile considering how much easier it'll be to switch between math libraries and the potential benefits. |
Just another note: There are also multiple instances of every index of an array being manually indexed. One example's here where every index of the for (let i = 0; i < 16; i++)
this.mat4[i] = arguments[i]; or the even simpler approach: this.mat4 = [...arguments]; |
@RandomGamingDev has raised a lot of excellent points in this thread. IMO we should rethink the design of the The current Most of the methods on the When looking at the current functions of the
A note on performance regarding that last point. The hard-coded approach is faster at runtime since there's no Looking at the current 4x4 only
3x3 only
both
[1] The entire matrix is replaced by |
Thanks for taking an interest in this @lindapaiste! I think the first big decision to make is whether we default to having all methods mutate the current matrix or whether all methods should all return new objects by default. Points for mutation:
Points for returning new objects:
I think this could still make sense as an instance method if we choose to keep matrix methods mutating the current object, similar to
I think this is an unfortunate consequence of the above point where it tries to use a mutate-first API like p5.Vector. The problem is that it goes from a 4x4 matrix to a 3x3 matrix, which would change the type of the current matrix. Instead, it ends up getting an argument, so that it can be a mutating method of a 3x3 matrix. If we decide to stick with an API based on mutation, maybe we can introduce a naming convention for methods that return new objects, like Alternatively, we could make all methods return new objects, similar to the native DOMMatrixReadOnly class. That might make more memory pressure when chaining together a few operations, so maaaaybe it's a bit slower? I'm not sure that that's a big enough issue for it to affect our decision though. |
I just want to mention quickly that for now, we can focus on optimizing the internal implementation if necessary but not to go too far in terms of new implementation and potential breaking changes. Mainly because I have something in the works to review this part of the code base as well that will give more flexibility to explore things further if necessary. |
I tend to agree with @GregStanton that we should probably relegate any significant expansion of p5.js' math API to add-on libraries. Don't get me wrong, I have a copy of Math Art on my coffee table, but most creative coders don't need anything close to Math.js or TensorFlow.js most of the time. The "you aren't gonna need it" (YAGNI) principle applies. What do people think about adapting the three.js math folder to work with p5.js? I believe three.js has every core matrix math feature we need and then some. There may be some upsides to sharing a little DNA with that project specifically. We started discussing code style in #6607. @lindapaiste @RandomGamingDev I'd love to hear your opinions about p5.js source code. My opinions in that discussion are limited to documentation. Past contributors volunteered their time without many of JavaScript's nice modern features or a style guide to follow. Perhaps we can chart a better course moving forward. |
Response
Honestly, I personally believe that the option to use a math library (assuming that we use an external one in the future) should always be a choice regardless of whether or not you need it. However, I do understand what you, @nickmcintyre & @GregStanton are talking about. That's why I agreed that in order to keep the main API decently small and easily searchable while not overcrowding everything, and also maintaining the p5.js code asethetic we should keep the current p5.js wrapper, or something close to it and then wrap that around the external math library like I said here:
How this would solve the problems statedThis would solve all of our problems with the idea, like the idea that it'd be hard to approach as a beginner and might even intimidate them, and that it'd be too hard to search through. This will allow us to take the best of both sides, providing beginners with the same, or maybe make it even easier to use while providing powerful mathematical functions and interoperability for those who need the library. The current math library (
|
I'm looking at the three.js code now. They have separate classes for Other than that it actually seems very similar to our code, including the parts that I don't like lol. For example they also have a They also have code like this 📜 which is 20 lines that could easily be a 3-line loop. Maybe for performance? It does feel a bit more readable overall. The intermediate assignment of |
Hi @RandomGamingDev! Thanks so much for your thoughtful reply. It's really nice to be able to see this issue from different sides while working toward a consensus. There's some chance that I'm misunderstanding your proposal. For example, it sounds like you're talking about namespacing the math functions? If so, how do you see that working with the current API, where the math functions are all in the global namespace (at least in global mode). Do you think you could post a code snippet that outlines the user-facing API you have in mind? Maybe you could just include a representative sample of the types of functions you'd like to see, and include some comments where you think other functions should probably be added. Seeing your proposal concretely like this should help us to be sure we're talking about the same thing. |
Also @lindapaiste, yeah, I suspect the code snippet you shared from three.js is like that for performance reasons. I didn't go through it all in detail, but it seems like a good bet to me because (a) whoever wrote that code almost certainly knows about loops and probably chose not to use one for a reason, and (b) the related glMatrix library is quite popular and applies the principle "Unroll EVERYTHING" for the sake of speed. A blog post from the creator of glMatrix says there's not a single loop in the code (the post is old, but we could inspect the code to see if that's changed). So speed seems like it's probably the reason. |
I will also mention that while I don't think we need to go so far as to avoid loops in our code, the last time I was doing a bunch of matrix code in js from scratch, I found that if I didn't try to reuse the same matrix object where possible, itd end up with stuttery animation. In a chrome profile, it seemed that while most frames were fast, every garbage collection caused a frame or two to be dropped. It still would happen occasionally even with reused matrices, but it happened much less in that one project. That's not to say that we absolutely have to do the same thing in p5, since we might be unavoidably doing some of that in push/pop stacks? But probably worth stress testing a bit before committing to one way or another. |
Here's a demo of what I'm thinking of with one of the possible functions (in this case the
Yes, I'm discussing namespacing the math functions in order to introduce what p5.js needs while still keeping the API small, the code in that class p5.js style, and also providing interoperability with a more powerful math library like |
Also, I'd just like to note that I'm actually fine with things being unrolled for performance reasons, however, I do believe that code quality is still the most important and that three.js did a good job maintaining that code quality. p5.js's code doesn't have that level of quality, nor is p5.js a library that really has speed as its first priority. Not to mention that p5.js's code is likely less efficient rather than more. So while I'm fine and actually in many cases endorse unrolled code for the sake of speed, I don't believe that that fits the coding clarity and priority on understandability that p5.js's doctrine is all about and the developers, whose main priority is creating a great and understandable processing library with new features, not remaking math functions already made in other libraries. That's why I think that it'd be better to leave things like the intricacies of these math functions and their design choices, whether for performance or clarity to larger, more general, more performant, and more powerful math libraries. |
@RandomGamingDev and @lindapaiste: Thanks for taking the time to flesh out your points! I think it's worthwhile to question the idea that performance is not really a top priority relative to subjective measures of codebase readability. This may sound controversial! So I'll explain what I mean. Performance is an accessibility issue I'll quote @nickmcintyre, who made some compelling points on this topic:
People with less powerful devices (for example, people who can only access the p5.js Web Editor on a mobile phone) may face a variety of barriers. Not being able to run code due to performance would make that list longer. Their devices may also tend to be older, which means they may have trouble holding a charge. So power consumption also has immediate consequences on accessibility, in addition to the global considerations. Performance and readability are not always mutually exclusive Performance is valued in p5.js In practice, we don't have to look very far to see performance being emphasized in p5.js. The biggest focus right now is the p5.js 2.0 RFC that @limzykenneth just released, and it cites performance as the reason for multiple changes. It also explicitlly calls for an effort to identify performance optimizations in a new math module, which is the issue we're discussing here. Clarifying update to the p5.js contributor guide? |
Sorry for the long wait, I've been pretty busy recently.
Because of your point here I created a benchmark for calculating the projection of 1000 4d hypercubes to demonstrate what I meant by "4d scenes" as @GregStanton requested all of which were ran on Version 115.6.0esr (64-bit) of Firefox and all on the same machine under the same conditions alongside the benchmarks themselves (there's some extra non-math stuff which I will explain in a moment): This benchmark includes results for:
with:
Now something that might get asked is "Why wouldn't I just do this on the GPU" and the answer is that this is supposed to be a benchmark, and also that the vertices are oftentimes needed, for instance, if we were actually using 4d projections for a project we might want this for say, collisions, which would be best done on the CPU. Something that you might notice is that math.js is horridly ~28.6ms slower for the 1000 hypercubes aka 0.0286 milliseconds or 28600 nanoseconds slower per hypercube 😱 (so not actually that bad).
While I do agree that performance is an accessibility issue, the issue is getting made out to be worse than it actually is when the main issue is clearly the readability and manpower spent deciphering the code base, which dissuades people from contributing to the library. In conclusion, p5.Math clearly demonstrates the bad design choices for not only usability, but performance that get made when developing a library "along the way" like what p5.js has been doing and plans to do again and the performance "issues," if we're going to talk about them despite every other math library other than math.js being faster, with math.js only being hundreths of a ms slower (per hypercube in this example) when using math.js for everything including what it wasn't designed for clearly apply to the math portion much less than when compared to p5's rendering. https://github.com/pppp606/numjs/tree/fix/multiply_and_divide_argument_types
I never argued that performance and readability are mutually exclusive and in fact happily support code unrolling and believe that it can be quite readable like in my previous comment.
projected.forEach((l, v, i) => v * w.getNum([i[0], 0])) although the difference in readability isn't that much once comments are added into the mix, like how for this example Personally, I'd exclude math.js for now assuming that p5.js doesn't care about the math visualization side enough to make the change for them, choose tfjs (cpu backend) if p5.js wants to be extra dynamic, glMatrix if p5.js wants to be extra performant, and MatrixJs if p5.js wants something inbetween for dynamicism and performance. If I had to guess, based off of what I've seen I think it's best to either go with glMatrix or MatrixJs.
|
If we're not going to expand the feature set of our math API, I'd support replacing internals with glMatrix calls surrounded by the friendly errors + unit conversion code we currently have as a way of providing the same features with better performance. I don't have a very strong opinion either way in expanding the math operations to support more arbitrary matrix sizes and more linear algebra. If we opt to not do it now, and instead use something like glMatrix, how feasible would it be for an add-on library to interoperate with that format? Using a mathjs-like library now definitely keeps that door open, but do others do that too?
I just want to mention that not having the bandwidth to make those changes between last summer and now doesn't mean it isn't important to us, just that for now there have been lower hanging fruit given our limited resources. Although I think it's probably true in general that we don't have a super clear stance on when to prioritize performance. |
That sounds perfect, although I do think that we should expand our math API so support more features.
My opinion on this is that it should be done especially considering the fact that p5.js is oftentimes used for visualizations that rely heavily on math and that would benefit heavily from that. My thoughts on using
Don't worry, I know that resources are limited. I just meant that p5.js clearly isn't flocking over this issue declaring it "an accessibility issue" so why should we say using a slightly slower math library is one when a slightly lower math library is much less of a performance issue or "accessibility issue" relative to that? And yeah I agree, p5.js doesn't really care about performance so much as to deem that small of a change in performance an "accessibility issue". |
Increasing Access
The current p5.js math component is heavily cluttered and isn't nearly as optimized as it could be which makes it hard to not only use if you're trying to understand and interact with the code, but also to contribute to, especially for a library designed to increase accessibility to beginners.
Plus, it's hard to integrate p5.js with other math libraries when you need any math that's more complex, for instance if someone wanted to do something that involved linear algebra, or calculus that isn't included in p5.js, like 4d scenes.
Not only would using another library mean a better documented and managed library with more features for p5.js, but it'll also be more performant, and we could choose a library that can utilize the GPU for especially heavy operations.
Most appropriate sub-area of p5.js?
Feature enhancement details
Instead of using p5.js's current math component, revamp it so that it uses another more powerful math library for its calculations.
The text was updated successfully, but these errors were encountered: