Skip to content

Packed*Array CoW behavior doesnt mirror GDScript's #1968

Open

Description

@coyo-t

Godot version

4.6

godot-cpp version

4.6

System information

win 10

Issue description

gdscript example that works as expected

class Something:
	func addToPacked (a:PackedVector2Array) -> void:
		a.append(Vector2.ONE)

	func addToTyped (a:Array[Vector2]) -> void:
		a.append(Vector2.ONE)

var it := Something.new()
var a := PackedVector2Array()
var b: Array[Vector2] = []

it.addToPacked(a)
it.addToTyped(b)

# a is [(1,1)]
# b is [(1,1)]

recreating in C++ (with other stuff omitted for brevity)

auto Something::addToPacked (PackedVector2Array array) -> void {
	array.append(Vector2(1,1));
}

auto Something::addToTyped (TypedArray<Vector2> array) -> void {
	array.append(Vector2(1,1));
}

auto Something::_bind_methods () -> void {
	ClassDB::bind_method(D_METHOD("addToPacked", "array"), &Something::addToPacked);
	ClassDB::bind_method(D_METHOD("addToTyped", "array"), &Something::addToTyped);
}

doing the above gdscript snippet with the gdextension version, a will be [] and b will be [(1,1)]
afaict the reason is that the packed array is having its Copy-on-Write triggered when it probably shouldnt be? (since both the above purely gdscript version as well as the docs say that packed arrays are passed by reference)
extremely obnoxious if intended (since the docs say that packed arrays are passed by reference, which they do in the GD version but not the C version)

Steps to reproduce

^

Minimal reproduction project

N/a

Activity

  1. Ivorforce commented on Apr 10, 2026

    @Ivorforce
    Member
  2. coyo-t commented on Apr 10, 2026

    @coyo-t
    Author

    yeah okay thats peak obnoxiousness .-. thanks anyway
    for reference im using "out" arrays for building geometry data; current version is:

    auto CubeModelBuilder::createFace (
    	int cardinal,
    	TypedArray<Vector3> intov,
    	TypedArray<Vector2> intou,
    	TypedArray<Vector3> inton,
    	TypedArray<int32_t> intoi
    ) -> void

    the GD version used packed arrays but yeah that didnt work when ported. Cool. Great. -.-
    guess this is why the packed arrays dont have bulk copy from->into methods ¬_¬

  3. dsnopek commented on Apr 10, 2026

    @dsnopek
    Collaborator

    @Ivorforce I vaguely recall there being a workaround for this, I think by using Variant as the argument rather than the packed array type?

  4. Ivorforce commented on Apr 10, 2026

    @Ivorforce
    Member

    Yes, this helps if you then use VariantInternal to grab the pointer to the packed array inside.
    However, the consistency is limited since it only works if you're getting call'd (e.g. from GDScript). If someone uses ptrcall, the semantics flip, and this workaround won't work.

  5. coyo-t commented on Apr 13, 2026

    @coyo-t
    Author

    had some previous experience connect in my head that might be a fine soultion to this
    so in old versions of GML, arrays passed to functions/scripts had this same CoW behavior (tmk it wasnt for multithreading though it was one of the many things inherited by its original parent Delphi), but there was a way to manually override the behavior. i dont think it had a name but it could be called the "array accessor" or "at array", idk

    function something (_input) {
        var a = _input[0] // doesnt trigger cow, Duh.
        _input[@ 1] = 10 // doesnt trigger, uses the [@ accessor
        _input[2] = 20 // triggers, now operating on a copy of the array rather than itself
    }

    i dont know what to call this for GD++ but im thinking something like this?

    auto something (PackedByteArray input) -> void {
        auto a = input[0]; // no COW
        auto middlethem = input.cow_tools_jpg(); // terrible name, idk what to call it though
        middlethem.append_array({ 2, 9, 2 }); // no COW triggered
        input.append({ 2, 0, 2 }); // COW triggered, input is now a copy of the data (though it has middle's changes)
    }

    if i find it in me ill try to make my own version of this as a PR or something
    i think the hardest part of this will be thinking of a name for it
    get_cowless_proxy? nehh
    get_explicitly_writable?
    get_thread_unsafe_writable?
    get_version_that_lets_me_write_to_you_without_triggering_copy_on_write_you_gormless_construct?
    :P i dont think the COW behavior should be toggleable by the user on the object itself though. i feel like that causes more problems than it solves. i also dont think a PackedByteArray and a PBA_Cowless should be interchangable (but they can be convertable to and from using the get_* methods)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions