SceneKit:持续存在太多内存

时间:2016-02-28 19:21:27

标签: memory scenekit

我在这里没有想法,SceneKit正在堆积内存,我才开始。我正在显示存储在数组中的SNCNodes,因此我可以分离分子的组件以进行动画处理。这些树模型分子我最终可能有50个显示,比如每个“章节”一个。问题是,当我转到另一章时,前面章节中的分子仍然存在于记忆中。

分子节点是子节点的树。大约一半的节点是空容器,用于定位。否则,几何形状为SCNPrimitives(球体,胶囊和圆柱体)。每个几何体都有一个镜面和一个由material组成的漫反射UIColor,没有使用纹理。

当应用程序首次启动时,这些分子由代码构建并存档到字典中。然后,在随后的引导中,存档的字典被读入本地字典以供VC使用。 (为简洁起见,我在本文中删除了安全功能。)

moleculeDictionary = Molecules.readFile() as! [String: [SCNNode]]

当章节想要显示分子时,它会调用一个特定的函数,将给定分子所需的组分从本地字典加载到本地SCNNode属性中。

// node stores (reuseable)
var atomsNode_1 = SCNNode() 
var atomsNode_2 = SCNNode()
        . . .

func lysozyme() {   // called by a chapter to display this molecule 
        . . .
    components = moleculeDictionary["lysozyme"]

    atomsNode_1 = components[0]         // protein w/CPK color
    baseNode.addChildNode(atomsNode_1)
    atomsNode_2 = components[2]         // NAG
    baseNode.addChildNode(atomsNode_2)
        . . .
 }

在显示下一个分子之前,我称之为“清理”功能:

atomsNode_1.removeFromParentNode()
atomsNode_2.removeFromParentNode()
        . . .

当我在乐器中进行调查时,大多数臃肿的内存是由C3DMeshCreateFromProfile调用的32 kB块和C3DMeshCreateCopyWithInterleavedSources的80 kB块。

我还需要跟踪哪些漏洞可追溯到归档的NSKeyedUnarchiver解码。所以我也需要处理这些,但它们只是累积每个分子调用的内存使用量的一小部分。

如果我回到之前查看的分子,内存使用量没有进一步增加,它会累积并持续存在。

我尝试声明atomsNode_1及其亲属作为选项,然后在清理时将它们设置为零。没有帮助。我已经尝试过,在清理功能中,

atomsNode_1.enumerateChildNodesUsingBlock({
    node, stop in
    node.removeFromParentNode()
})

好吧,内存会回落,但节点似乎永远不会从加载的字典中消失。该死的参考类型!

所以我可能需要一种方法来存档[SCNNode]数组,以便单独解压缩和检索它们。在这种情况下,我会在完成后清除它们的内存,并在重新访问该分子时从存档重新加载。但我知道还没怎么做这些。在投入更多时间感到沮丧之前,我对此表示赞赏。

2 个答案:

答案 0 :(得分:4)

球体,胶囊和圆柱都有相当密集的网格。你需要所有细节吗?尝试减少各种细分计数属性(segmentCountradialSegmentCount等)。作为快速测试,将SCNPyramid替换为所有基本类型(即具有最低向量计数的基元)。如果这是一个因素,你应该会看到内存使用的显着减少(它看起来很难看,但会立即给你关于你是否在可用轨道上的反馈)。你可以使用长SCNBox而不是圆柱吗?

另一个优化步骤是使用SCNLevelOfDetail来允许当物体远离时替代的低顶点计数几何体。这比简单地统一减少分段计数要多得多,但如果你有时需要更多的细节,那将会有所回报。

不是自己在数组中管理组件,而是使用节点层次结构来执行此操作。创建每个分子,或分子的可动画片段,作为SCNNodes的树。给它起个名字。制作一个flattenedClone。现在存档。需要时从存档中读取节点树;不要担心节点数组。

考虑编写两个程序。一个是操纵/显示分子的iOS程序。另一个是Mac(或iOS?)程序,它可以生成分子节点树并将其归档。这将为您提供一组SCNNode树存档,您可以将它们作为资源嵌入到显示程序中,而不需要动态生成。

scene kit memory management using swift的回答指出需要排除“纹理”(materialsfirstMaterial属性?)以释放节点。看起来值得一看,虽然因为你刚刚使用UIColor,我怀疑它是一个因素。

以下是创建复合节点并将其归档的示例。在实际代码中,您将归档与创建分开。还要注意使用长瘦框来模拟一条线。尝试倒角半径为0!

extension SCNNode {

public class func gizmoNode(axisLength: CGFloat) -> SCNNode {
    let offset = CGFloat(axisLength/2.0)
    let axisSide = CGFloat(0.1)
    let chamferRadius = CGFloat(axisSide)

    let xBox = SCNBox(width: axisLength, height: axisSide, length: axisSide, chamferRadius: chamferRadius)
    xBox.firstMaterial?.diffuse.contents = NSColor.redColor()
    let yBox = SCNBox(width: axisSide, height: axisLength, length: axisSide, chamferRadius: chamferRadius)
    yBox.firstMaterial?.diffuse.contents = NSColor.greenColor()
    let zBox = SCNBox(width: axisSide, height: axisSide, length: axisLength, chamferRadius: chamferRadius)
    zBox.firstMaterial?.diffuse.contents = NSColor.blueColor()
    let xNode = SCNNode(geometry: xBox)
    xNode.name = "X axis"
    let yNode = SCNNode(geometry: yBox)
    yNode.name = "Y axis"
    let zNode = SCNNode(geometry: zBox)
    zNode.name = "Z axis"

    let result = SCNNode()
    result.name = "Gizmo"
    result.addChildNode(xNode)
    result.addChildNode(yNode)
    result.addChildNode(zNode)
    xNode.position.x = offset
    yNode.position.y = offset
    zNode.position.z = offset

    let data = NSKeyedArchiver.archivedDataWithRootObject(result)
    let filename = "gizmo"

    // Save data to file
    let DocumentDirURL = try! NSFileManager.defaultManager().URLForDirectory(.DocumentDirectory, inDomain: .UserDomainMask, appropriateForURL: nil, create: true)

    // made the extension "plist" so you can easily inspect it by opening in Finder. Could just as well be "scn" or "node"
    // ".scn" can be opened in the Xcode Scene Editor
    let fileURL = DocumentDirURL.URLByAppendingPathComponent(filename).URLByAppendingPathExtension("plist")
    print("FilePath:", fileURL.path)

    if (!data.writeToURL(fileURL, atomically: true)) {
        print("oops")
    }
    return result
}
}   

答案 1 :(得分:1)

我在我的应用程序中也经历了很多来自SceneKit的内存膨胀,其中的内存块与您在Instruments(C3DGenericSourceCreateDeserializedDataWithAccessorsC3DMeshSourceCreateMutable等中的内存块相似。我发现在允许Swift取消初始化它们之前,在geometry对象上将nil属性设置为SCNNode

在您的情况下,在您的清理功能中,执行以下操作:

atomsNode_1.removeFromParentNode()
atomsNode_1.geometry = nil
atomsNode_2.removeFromParentNode()
atomsNode_2.geometry = nil

另一个如何实施清洁的例子:

class ViewController: UIViewController {
    @IBOutlet weak var sceneView: SCNView!
    var scene: SCNScene!

    // ...

    override func viewDidLoad() {
        super.viewDidLoad()
        scene = SCNScene()
        sceneView.scene = scene

        // ...
    }

    deinit {
        scene.rootNode.cleanup()
    }

    // ...
}

extension SCNNode {
    func cleanup() {
        for child in childNodes {
            child.cleanup()
        }
        geometry = nil
    }
}

如果这不起作用,可以通过将其纹理设置为nil来获得更好的成功,如scene kit memory management using swift所述。