feat: Add VectorValue for allocation-free vector math - #4023
Draft
spydon wants to merge 1 commit into
Draft
Conversation
Add VectorValue, an immutable 2D vector backed by Float64x2 that the VM keeps unboxed, and a value property on Vector2 that reads and writes the vector as a VectorValue, so that expressions such as `position.value = size.value / 2 + offset + velocity * dt` run without allocating while Vector2 and NotifyingVector2 remain the observable storage. Claude-Session: https://claude.ai/code/session_01PixXaRTPs8v1P2mQF2QHGG
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Stacked on #4022. Adds
VectorValue, an immutable 2D vector that is free to create and combine, and avalueproperty on everyVector2that reads and writes its components as aVectorValue. Together they make vector expressions allocation-free while keepingVector2/NotifyingVector2as the observable, mutable storage that components expose today. Nothing existing changes; this is additive.VectorValueis an extension type overFloat64x2, which the Dart VM keeps unboxed in locals, arguments, return values and fields, so a whole expression stays in registers. Every member carries@pragma('vm:prefer-inline'); without inlining each operator would return a boxed value, so the pragmas are part of the design. It keeps full double precision (VectorValue(0.1, 0).x == 0.1;Vector2stores floats).Assigning to
valuegoes throughsetValues, so aNotifyingVector2notifies its listeners exactly once per assignment,versionincrements once, andTransform2Dpicks the change up as usual.What it looks like
position.add(velocity * dt);position.value += velocity * dt;position.setFrom((size / 2)..add(offset)..addScaled(velocity, dt));position.value = size.value / 2 + offset + velocity * dt;final d = target - position..normalize();final d = (target.value - position.value).normalized();position.addScaled(velocity, dt);size.addListener(_relayout);,size.x = 32;,position.setFrom(v);velocityandoffsetabove areVectorValuefields (final velocity = VectorValue(30, -20);); a storedVector2on the right-hand side of an operator needs.value(a.position.value - b.position.value), because Dart has no operator overloading by argument type.VectorValuealso has the usual helpers (dot,cross,length,normalized,withLength,rotated,lerp,distanceTo,angleTo,clamp,floor,equals,closeTo,copyInto,toOffset, …), all documented indoc/flame/other/util.md.Benchmarks
benchmark/notifying_vector2_benchmark.dart, 10 000PositionComponents,flutter teston macOS arm64, on top of #4022:position.add(velocity * dt)position.value += velocity * dtposition.addScaled(velocity, dt)position.setFrom((size / 2)..add(offset)..addScaled(velocity, dt))position.value = size.value / 2 + offset + velocity * dtFor reference, on
maintheposition.add(velocity * dt)row measures 331 µs, so the readable form is now the fast form. Standalone AOT measurements of the same shapes (dart compile exe, minimum of 21 samples) put the.valueexpression at 6.5-8 ns per component against 17 ns for today'sNotifyingVector2chain, and confirm that theFloat64x2temporaries do not reach the heap.Caveats
==,hashCodeortoString, andFloat64x2compares by identity, soVectorValue(1, 2) == VectorValue(1, 2)isfalse. Useequals/closeTo;describe()gives a readable string; do not use it as a map key. Worth a lint inflame_lintlater.Float64x2has no const constructor, soVectorValue.zero/onearestatic finaland it cannot be a default parameter value.Float64x2is emulated; measured with dart2js it is still the fastest ergonomic option (8 ns for the full expression versus 22 ns for theVector2chain) as long as values are not stored in fields, whichVector2storage guarantees.Checklist
docsand added dartdoc comments with///.examplesordocs.Breaking Change?
Related Issues
Part of #3966. Stacked on #4022.
https://claude.ai/code/session_01PixXaRTPs8v1P2mQF2QHGG