GKObstacleGraph<GKGraphNode2D> does not handle archiving - ios

I'm using GameplayKit's GKObstacleGraph to add path finding support to my iOS 10.2 SpriteKit game.
The game is a top-down 2D game, it features impassable obstacles which my 'soldiers' can't walk through.
Creating a GKObstacleGraph for my game level, with about 500 obstacles, takes ~50 seconds. This is too long for my users to wait.
Since the game map layout never changes dynamically after being loaded, I decided to create the graph once:
let graph = GKObstacleGraph(obstacles: obstacles, bufferRadius: 5, nodeClass: GKGraphNode2D.self)
Archive it to file:
let directories = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
if let documents = directories.first {
if let filePath = String(documents + "/obstacle.graph") {
NSKeyedArchiver.archiveRootObject(graph, toFile: filePath)
}
}
Copy the file from device to my laptop and add the file to my bundle. Then I just unarchive the graph object when I load my game level:
if let filePath = Bundle.main.path(forResource: "obstacle", ofType: "graph") {
let graph = NSKeyedUnarchiver.unarchiveObject(withFile: filePath) as! GKObstacleGraph<GKGraphNode2D>
return graph
}
In theory, this should be much quicker since I don't have to calculate the graph, only read it(unarchive) from file.
However, depending on the number of obstacles and their relative placement one of three things happens:
NSKeyedArchiver.archiveRootObject crashes.
Archive works but NSKeyedUnarchiver.unarchiveObject crashes.
Both archive and unarchive works, but I can not find a path around obstacles with GKObstacleGraph.findPath
I can get all of this working if I skip the (un)archive steps:
Successful path finding
Also, on simulator (iPhone 7), (un)archive never crashes, but path finding always fails afterwards.
What is going on here? I've reported it as a bug to Apple, but I'm still hoping that I've missed something.
I tried alternative solutions to the problem where I'm writing/reading nodes and obstacles to file using my own format. But the obstacle property of GKObstacleGraph is get only, which leaves me with the constructor, and then I'm back to waiting 50 seconds again.
I've created a public test project git repo:
https://bitbucket.org/oixx/gkobstaclegraphcrashproof.git
which shows the three different scenarios that fails when running on device. For simplicity it reads/writes the file to device, not bundle.

I don't know anything about the crashes, but #3 in your list is probably caused by connectNodeToLowestCostNode:bidirectional: not actually connecting your soldier to the graph because the nodes array is immutable after unarchiving.
The solution is to archive the nodes instead of the graph, then just create a graph from the nodes
NSArray *nodes = [NSKeyedUnarchiver unarchiveObjectWithData:self.nodesArray];
GKGraph *graph = [[GKGraph alloc] initWithNodes:nodes];

Related

AKTableView issue 100% CPU usage

I'm trying to assign a table view to an already existing UIView object on my storyboard but every time I assign it by specifying the correct frame I get an issue.
let sampleTable = AKTableView(AKTable(file: sampleReferences[indexPath.row].sampleFile!), frame: samplePlot.bounds)
samplePlot.addSubview(sampleTable)
This is the code I'm using at the moment. I've also used samplePlot.frame.
Basically the sampleReferences[indexPath.row].sampleFile! is an AKAudioFile stored earlier from a recording stored as a reference from a UITableView element. It's quite a large program split into multiple files so it's hard to show everything.
The only extra issue I can think of is I haven't stopped any of the current AudioKit processes. I'm not sure if the table can only be drawn at startup.
The following works with a .sine specifier AKTable(.sine) so it's not an issue with my UI code. It also fails when trying to load even a basic .mp3 file from .resources.
Update:
I've compressed the test .mp3 file significatly and shortened it's overall length to 2 seconds and I'm still getting the same issue. I think it might be an issue with the actual function used to draw the file. Although I could be wrong.
Another update:
So everything works fine using the following code
let file = EZAudioFile(url: sampleAudio!.url)
guard let data = file?.getWaveformData() else {
print("Failed to get waveform data")
return }
let waveform = EZAudioPlot()
waveform.frame = samplePlot.bounds
waveform.plotType = EZPlotType.buffer
waveform.shouldFill = true
waveform.shouldMirror = true
waveform.color = UIColor.black
waveform.updateBuffer(data.buffers[0], withBufferSize: data.bufferSize )
samplePlot.addSubview(waveform)
but obviously I'm using EZAudioPlot. I grabbed this from a previous stack overflow issue for the same thing and edited it a bit.

File Not Created in iPhone Documents

I'm trying to save a bunch of GPS locations in a text file on an iPhone. I've been following suggestions that I've gleaned from the web, but, while the app runs fine, no file is being created. At least, nothing is visible under XCode Window -> Devices and Simulators -> (app name).
The goal is to create a file named "1" in a folder called "position" under a folder named after the date/time the app is launched, which is in turn under the Document folder. Example: 2018-07-21-16-35-43/position/1
I'm writing this in Swift 4.2 in XCode 9.(latest)
I get the Document folder as follows (rideStart = Date()):
let urls = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
if let docUrl = urls.first {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd-HH-mm-ss"
let dateText = dateFormatter.string(from: rideStart)
positionUrl = docUrl.appendingPathComponent(dateText)
}
When I'm ready to write out some lines of text I do the following (locationCache is an array of CLLocation objects, and textForFile is an extension property that formats the information I want as a comma-separated set of values, terminated with \r\n):
try? FileManager.default.createDirectory(at: positionUrl, withIntermediateDirectories: true)
let curUrl = positionUrl.appendingPathComponent("\(fileNumber)")
for loc in locationCache {
do {
let line = loc.textForFile
print(line)
try line.write(to: curUrl, atomically: true, encoding: .utf8)
}
catch {
print(error)
}
}
No errors are thrown when the code runs, and the text lines print to the console in the expected format.
I'm new to iOS and to Swift, the vast majority of my experience being in C# under Windows, along with javascript. I'm obviously missing something simple and/or basic. What am I doing wrong?
Additional Info
Based on feedback, I ran the app in a simulator. After figuring out where the iPhone 6 simulator was located in my Library folder, I determined that the file I was trying to create was, in fact, created (note: I'm currently using a FileHandle based approach to writing the file, as opposed to what's in the code shown in this posting -- but I've tested that FileHandle approach against the physical iPhone hardware, and it still doesn't create any file visible from within XCode's Devices and Simulators window.
Which brings up a point raised in the comments: is it even possible to examine the files on a connected/paired iPhone from within XCode? I thought from what I've read in various places that it should be possible...but that may not be the case.

Mixed topology (quad/tri) with ModelIO

I'm importing some simple OBJ assets using ModelIO like so:
let mdlAsset = MDLAsset(url: url, vertexDescriptor: nil, bufferAllocator: nil, preserveTopology: true, error: nil)
... and then adding them to a SceneKit SCN file. But, whenever I have meshes that have both quads/tris (often the case, for example eyeball meshes), the resulting mesh is jumbled:
Incorrect mesh topology
Re-topologizing isn't a good option since I sometimes have low-poly meshes with very specific topology, so I can't just set preserveTopology to false... I need a result with variable topology (i.e. MDLGeometryType.variableTopology).
How do I import these files correctly preserving their original topology?
I reported this as a bug at Apple Bug Reporter on 25th of November, bug id: 35687088
Summary: SCNSceneSourceLoadingOptionPreserveOriginalTopology does not actually preserve the original topology. Instead, it converts the geometry to all quads, messing up the 3D model badly. Based on its name it should behave exactly like preserveTopology of Model IO asset loading.
Steps to Reproduce: Load an OBJ file that has both triangles and polygons using SCNSceneSourceLoadingOptionPreserveOriginalTopology and load the same file into an MDLMesh using preserveTopology of ModelIO. Notice how it only works properly for the latter. Even when you create a new SCNGeometry based on the MDLMesh, it will "quadify" the mesh again to contain only quads (while it should support 3-gons and up).
On December 13th I received a reply with a request for sample code and assets, which I supplied 2 days later. I have not received a reply since (hopefully because they are just busy from catching up from the holiday season...).
As I mentioned in my bug report's summary, loading the asset with Model I/O does work properly, but then when you create a SCNNode based on that MDLMesh it ends up messing up the geometry again.
In my case the OBJ files I load have a known format as they are always files also exported with my app (no normals, colors, UV). So what I do is load the information of the MDLMesh (buffers, facetopology etc) manually into arrays, from which I then create a SCNGeometry manually. I don't have a complete separate piece of code of that for you as it is a lot and mixed with a lot of code specific to my app, and it's in Objective C. But to illustrate:
NSError *scnsrcError;
MDLAsset *asset = [[MDLAsset alloc] initWithURL:objURL vertexDescriptor:nil bufferAllocator:nil preserveTopology:YES error:&scnsrcError];
NSLog(#"%#", scnsrcError.localizedDescription);
MDLMesh * newMesh = (MDLMesh *)[asset objectAtIndex:0];
for (MDLSubmesh *faces in newMesh.submeshes) {
//MDLSubmesh *faces = newMesh.submeshes.firstObject;
MDLMeshBufferData *topo = faces.topology.faceTopology;
MDLMeshBufferData *vertIx = faces.indexBuffer;
MDLMeshBufferData *verts = newMesh.vertexBuffers.firstObject;
int faceCount = (int)faces.topology.faceCount;
int8_t *faceIndexValues = malloc(faceCount * sizeof(int8_t));
memcpy(faceIndexValues, topo.data.bytes, faceCount * sizeof(int8_t));
int32_t *vertIndexValues = malloc(faces.indexCount * sizeof(int32_t));
memcpy(vertIndexValues, vertIx.data.bytes, faces.indexCount * sizeof(int32_t));
SCNVector3 *vertValues = malloc(newMesh.vertexCount * sizeof(SCNVector3));
memcpy(vertValues, verts.data.bytes, newMesh.vertexCount * sizeof(SCNVector3));
....
....
}
In short, the preserveTopology option in SceneKit isn't working properly. To get from the working version in Model I/O to SceneKit I basically had to write my own converter.

Suddenly cannot read files anymore

I'm developing a tiny game and at first it works super perfect. It loads sks files and sound files pretty well. But after a few minutes of testing, I start to get "Error loading sound resource" and particle effects all disappear.
I thought it might be the memory problem, however, the game only takes about 120M, and I haven't received any memory warnings yet.
So how can it manages to read files in the beginning and fails to do so a few minutes later? Is there something tricky in SpriteKit that I don't know? Such as after loading one hundred files successfully, the game cannot read any files anymore until the user shakes the iPhone twice and turns it upside down... If it's true, I will possible inform the players to do so in the game's tutorial...
So can anyone help me handle this...? Lots of thanks...
// The way I load background music:
let musicPath=Bundle.main.path(forResource: "Main", ofType: "mp3")
let url = URL(fileURLWithPath: musicPath!)
do{
try main=AVAudioPlayer(contentsOf: url)
}catch{
return
}
// The way I load sound files:
let a = SKAction.playSoundFileNamed("Fire",waitForCompletion:false)
audioNode.run(a)
// The way I load SKS files:
let emitterPath = Bundle.main.path(forResource: "Fire", ofType: "sks")!
let emitter = NSKeyedUnarchiver.unarchiveObject(withFile: emitterPath) as! SKEmitterNode
self.addChild(emitter)
It's hard to say without seeing your code but if you're opening a lot of file readers you could run out of memory or make the files inaccessible until the other nodes reading from the files are gone.
Do you happen to know if you're creating new audio nodes every time they are rendered?
Apples documentation suggests that you create a single SKAction to be reused. So if you need to reuse your Fire action, I would create a singleton of it and run that single object over and over instead of allocating the new SKAction again and again.
Source

Dynamic naming of objects in AudioKit (SpriteKit)

I am trying to create an app similar to the Reactable.
The user will be able to drag "modules" like an oscillator or filter from a menu into the "play area" and the module will be activated.
I am thinking to initialize the modules as they intersect with the "play area" background object. However, this requires me to name the modules automatically, i.e.:
let osci = AKOscillator()
where osci will automatically count up to be:
let osci1 = AKOscillator()
let osci2 = AKOscillator()
...
etc.
How will I be able to do this?
Thanks
edit: I am trying to use an array by creating an array of
var osciArray = [AKOscillator]()
and in my function to add an oscillator, this is my code:
let oscis = AKOscillator()
osciArray.append(oscis)
osciArray[oscCounter].frequency = freqValue
osciArray[oscCounter].amplitude = 0.5
osciArray[oscCounter].start()
selectedNode.userData = ["counter": oscCounter]
oscCounter += 1
currentOutput = osciArray[oscCounter]
AudioKit.output = currentOutput
AudioKit.start()
My app builds fine, but once the app starts running on the Simulator I get error : fatal error: Index out of range
I haven't used AudioKit, but I read about it a while ago and I have quite a big interest in it. From what I understand from the documentation, it's structured pretty much like SpriteKit: nodes connected together.
I guess then that most classes in the library derive from a base class, just like everything in SpriteKit derives from the SKNode class.
Since you are linking the audio kit nodes with visual representations via SpriteKit nodes, why don't you simply subclass from an SKSpriteNode and add an optional audioNode property with the base class from AudioKit?
That way you can just use SpriteKit to interact directly with the stored audio node property.
I think there's a lot of AudioKit related code in your question, but to answer the question, you only have to look at oscCounter. You don't show its initial value, but I am guessing it was zero. Then you increment it by 1 and try to access osciArray[oscCounter] which has only one element so it should be accessed by osciArray[0]. Move the counter lower and you'll be better off. Furthermore, your oscillators look like local variables, so they'll get lost once the scope is lost. They should be declared as instance variables in your class or whatever this is part of.

Resources