I posted this question on stack overflow the other day but haven't received any replies as yet. I want to be able to access SpriteKit node characteristics on a background thread. I only require a copy of these characteristics but don't know how to do this. The full question was:
I’m working on my first Swift project. It uses SpriteKit and has quite a few vehicles moving around different tracks. Each vehicle must be aware of other vehicles to avoid collisions and for overtaking etc. The processing for this is very time consuming so I want to run most of the calculations concurrently on a number of background threads.
To do this, I copied the vehicles defined in the arrays track1Vehicles and track2Vehicles (arrays of SKSpriteNodes) to t1Vehicle and t2Vehicle. The idea was to perform all the calculations on these, then afterwards transfer the new positions etc back to the original arrays using SKActions on the main thread. The problem I’m having is that any reference to t1Vehicle and t2Vehicle parameters gives the error “Main actor-isolated property 'goalSpeed' can not be mutated from a non-isolated context” (goalSpeed being one of those parameters). Is there a way to allow t1Vehicle and t2Vehicle on a background thread, perhaps by disabling their physicsBodies or similar?
t1Vehicle & t2Vehicle are not intended to themselves alter the display. They are intended only as a means of accessing the data as it was when the ‘findObstacles’ function was called. If there’s another way to reference this data, that would also solve the problem.
The relevant parts of the code are shown below:
//this code is on the main thread
Task {
let result = await findObstacles()
let t1Vehicle = result.t1Vehicle
let t2Vehicle = result.t2Vehicle
}
//Function called from the Task above
func findObstacles() async -> (t1Vehicle: Vehicle, t2Vehicle: Vehicle) {
//Create copy of vehicles for calculating proximity to other vehicles
//Do calculations on another thread
var t1Vehicle = track1Vehicles.dropFirst() //Straight Track Vehicles: Ignore element [0]
t1Vehicle.sort(by: {$0.position.y < $1.position.y}) //Sort into positional order, 000 - 999.999
var t2Vehicle = track2Vehicles.dropFirst()
t2Vehicle.sort(by: {$0.position.y < $1.position.y}) //Sort into positional order, 000 - 999.999
//processing of data here..
return (t1Vehicle, t2Vehicle)
}
ps. the 'Vehicle' type is an ObservableObject. A number of its parameters were also Published variables. I tried removing the '@Published' from the parameter 'goalSpeed' and it made no difference - the error still occurred on it.
I'm getting the impression that nodes can only be accessed from the main thread. With my code I am copying 2 arrays of nodes and use those copies in the background thread/s. The original arrays are not altered. They could even be copied prior to going into the background thread. I need access to things such as position and velocity for each node and there can potentially be 300 of them. A fast & simple way to copy these parameters from all nodes into a normal array would do. Currently my copy still contains nodes even though I don't display them and only require read access.
I was beginning to think the only way I could achieve a result was by iterating over every
SKNode, extracting information such as position, speed and size then writing all this into another array.
There are two ways you can approach APIs like SpriteKit:
-
You can keep all your state in the SpriteKit nodes. If you do that then, yeah, you will find yourself having to read data out of those nodes if you want to do other custom processing on it.
-
You can keep all of your state in your own model objects and then push any changes to the SpriteKit nodes.
Which approach to choose depends on how much SpriteKit is doing for you. If you can do the vast bulk of your processing in SpriteKit, then the first option is ideal. If not, the second option tends to be a better choice.
Coming back to this specific error:
The second line has the error
Cannot assign value of type '[Any]' to type 'Array.SubSequence' (aka ‘ArraySlice’).
You’re getting this because Array and ArraySlice are different things. Consider this snippet:
let i = [1, 2, 3]
let s = i.dropFirst()
If you option click on i, it’ll display a type of [Int], that is, Array<Int>. If you option click on s, you get Array<Int>.SubSequence, aka ArraySlice<Int>. This is a common source of confusion in Swift.
The difference exists for performance reasons. Compare these two snippets:
Array(i.dropFirst().dropLast())
Array(Array(i.dropFirst()).dropLast())
In the first example, all the intermediate processing is done with slices and there’s a single conversion back to an array at the end. This can be a huge performance win.
In contrast, the second example shows what you get in other languages, ones without the concept of slices. Each processing operation creates a new array, which can be expensive.
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"