javascript - La recolección de basura QML elimina los objetos que aún están en uso
qt garbage-collection (2)
Cree una matriz dentro de un archivo .js y luego cree una instancia de esa matriz con
var myArray = [];
en el nivel superior de ese
.js.
expediente.
Ahora puede hacer referencia a cualquier objeto que agregue a
myArray
incluidos los que se crean dinámicamente.
La recolección de basura no elimina los vars de Javascript siempre que permanezcan definidos, por lo que si define uno como un objeto global, entonces incluya ese archivo Javascript en su documento qml, permanecerá mientras el QML principal esté dentro del alcance.
En un archivo llamado: backend.js
var tiles = [];
function create_square(new_square) {
var component = Qt.createComponent("qrc:///src_qml/src_game/Square.qml");
var sq = component.createObject(background, { "backend" : new_square });
sq.x = new_square.tile.x
sq.y = new_square.tile.y
sq.width = new_square.tile.width;
sq.height = new_square.tile.height;
tiles[game.board.getIndex(new_square.tile.row, new_square.tile.col)] = sq;
sq.visible = true;
}
EDITAR :
Permítanme explicar un poco más claramente cómo esto podría aplicarse a su ejemplo particular de árbol.
Al usar la
property Item object
línea
property Item object
lo está forzando inadvertidamente a ser una propiedad de Item, que se trata de manera diferente en QML.
Específicamente, las propiedades caen bajo un conjunto único de reglas en términos de recolección de basura, ya que el motor QML simplemente puede comenzar a eliminar las propiedades de cualquier objeto para disminuir la memoria requerida para ejecutarse.
En su lugar, en la parte superior de su documento QML, incluya esta línea:
import "./object_file.js" as object_file
Luego, en el archivo object_file.js , incluya esta línea:
var object_hash = [];
Ahora puede usar
object_hash
cualquier momento para guardar sus componentes creados dinámicamente y evitar que se
object_hash
haciendo referencia al
object_file.object_hash
objeto.
No hay necesidad de volverse loco cambiando de dueño, etc.
Me he encontrado con este problema en varias ocasiones, con objetos creados dinámicamente, independientemente de si se crearon en QML o C ++. Los objetos se eliminan mientras aún están en uso, lo que provoca accidentes graves sin razón aparente. Los objetos todavía están referenciados y pareados a otros objetos hasta el objeto raíz, por lo que me resulta extraño que QML elimine esos objetos mientras su recuento aún está por encima de cero.
Hasta ahora, la única solución que encontré fue crear los objetos en C ++ y establecer la propiedad en CPP explícitamente, lo que hace imposible eliminar los objetos de QML.
Al principio supuse que podría ser un problema con la crianza de los hijos, ya que estaba usando clases derivadas de
QObject
, y el método QML de instanciación dinámica pasa un
Item
para un padre, mientras que
QtObject
ni siquiera viene con una propiedad padre, no está expuesto de
QObject
.
Pero luego probé con un
Qobject
derivado que expone y usa la crianza de los hijos y finalmente intenté usar
Item
solo para asegurarme de que los objetos están correctamente pareados, y sin embargo, este comportamiento aún persiste.
Aquí hay un ejemplo que produce este comportamiento, desafortunadamente no pude aplanarlo a una sola fuente porque la anidación profunda del
Component
s lo rompe:
// ObjMain.qml
Item {
property ListModel list : ListModel { }
Component.onCompleted: console.log("created " + this + " with parent " + parent)
Component.onDestruction: console.log("deleted " + this)
}
// Uimain.qml
Item {
id: main
width: childrenRect.width
height: childrenRect.height
property Item object
property bool expanded : true
Loader {
id: li
x: 50
y: 50
active: expanded && object && object.list.count
width: childrenRect.width
height: childrenRect.height
sourceComponent: listView
}
Component {
id: listView
ListView {
width: contentItem.childrenRect.width
height: contentItem.childrenRect.height
model: object.list
delegate: Item {
id: p
width: childrenRect.width
height: childrenRect.height
Component.onCompleted: Qt.createComponent("Uimain.qml").createObject(p, {"object" : o})
}
}
}
Rectangle {
width: 50
height: 50
color: "red"
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.RightButton | Qt.LeftButton
onClicked: {
if (mouse.button == Qt.RightButton) {
expanded = !expanded
} else {
object.list.append({ "o" : Qt.createComponent("ObjMain.qml").createObject(object) })
}
}
}
}
}
// main.qml
Window {
visible: true
width: 1280
height: 720
ObjMain {
id: obj
}
Uimain {
object: obj
}
}
El ejemplo es un generador de árbol de objetos trivial, con el botón izquierdo agregando una hoja al nodo y el botón derecho contrayendo el nodo. Todo lo que se necesita para reproducir el error es crear un nodo con una profundidad de 3 y luego colapsar y expandir el nodo raíz, sobre el cual la salida de la consola muestra:
qml: created ObjMain_QMLTYPE_0(0x1e15bb8) with parent QQuickRootItem(0x1e15ca8)
qml: created ObjMain_QMLTYPE_0(0x1e5afc8) with parent ObjMain_QMLTYPE_0(0x1e15bb8)
qml: created ObjMain_QMLTYPE_0(0x1e30f58) with parent ObjMain_QMLTYPE_0(0x1e5afc8)
qml: deleted ObjMain_QMLTYPE_0(0x1e30f58)
El
object
del nodo más profundo se elimina sin razón alguna, aunque esté parental al elemento del nodo primario y se haga referencia en el objeto JS en el modelo de lista.
Intentar agregar un nuevo nodo al nodo más profundo bloquea el programa.
El comportamiento es consistente, independientemente de la estructura del árbol, solo el segundo nivel de nodos sobrevive, todos los nodos más profundos se pierden cuando el árbol se colapsa.
La falla no radica en el modelo de lista que se usa como almacenamiento, lo probé con una matriz JS y una
QList
y los objetos aún se pierden.
Este ejemplo utiliza un modelo de lista simplemente para guardar la implementación adicional de un modelo C ++.
El único remedio que encontré hasta ahora fue negar la propiedad de QML de los objetos por completo.
Aunque este ejemplo produce un comportamiento bastante consistente, en el código de producción las eliminaciones espontáneas son a menudo completamente arbitrarias.
Con respecto al recolector de basura, lo he probado antes y me di cuenta de que es bastante liberal: la creación y eliminación de objetos con un valor de 100 MB de ram no activó la recolección de basura para liberar esa memoria y, en este caso, solo unos pocos Los objetos, con un valor de unos cientos de bytes, se eliminan rápidamente.
De acuerdo con la documentación, los objetos que tienen un padre o que JS hace referencia no deben eliminarse, y en mi caso, ambos son válidos:
El objeto es propiedad de JavaScript. Cuando el objeto se devuelve a QML como el valor de retorno de una llamada al método, QML lo rastreará y lo eliminará si no hay referencias de JavaScript restantes y no tiene QObject :: parent ()
Como se menciona en la respuesta de Filip, esto no sucede si los objetos son creados por una función que no está en un objeto que se elimina, por lo que puede tener algo que ver con el estado JS vagamente mencionado asociado con los objetos QML, pero estoy esencialmente todavía en la oscuridad de por qué ocurre la eliminación, por lo que la pregunta sigue sin respuesta.
¿Alguna idea de que causa esto?
ACTUALIZACIÓN: Nueve meses después, todavía no hay desarrollo en este error crítico . Mientras tanto, descubrí varios escenarios adicionales donde se eliminan los objetos que todavía están en uso, escenarios en los que no importa dónde se creó el objeto y no se aplica la solución para crear simplemente los objetos en el archivo qml principal. La parte más extraña es que los objetos no se destruyen cuando se los "no hace referencia" sino que se los "vuelve a hacer referencia". Es decir, no se destruyen cuando los objetos visuales que los hacen referencia se destruyen, sino cuando se vuelven a crear.
La buena noticia es que todavía es posible establecer la propiedad en C ++ incluso para los objetos que se crean en QML, por lo que no se pierde la flexibilidad de la creación de objetos en QML. Existe el inconveniente menor de llamar a una función para proteger y eliminar cada objeto, pero al menos evita la gestión de errores de por vida de QtQuick. Sin embargo, me encanta la "conveniencia" de QML: ser obligado a volver a la gestión manual de la vida útil de los objetos.
QML no es C ++ en una forma de administrar la memoria. QML tiene la intención de ocuparse de asignar memoria y liberarla. Creo que el problema que encontraste es solo el resultado de esto.
Si la creación dinámica de objetos es demasiado profunda, todo parece eliminarse. Por lo tanto, no importa que los objetos creados sean parte de los datos, también se destruyen.
Lamentablemente mi conocimiento termina aquí.
Una de las soluciones al problema (lo que demuestra mi afirmación anterior) es mover la creación de la estructura de datos fuera de los archivos dinámicos de la interfaz de usuario qml:
- Coloque la función de creación de objetos, por ejemplo, en main.qml
function createNewObject(parentObject) {
parentObject.list.append({ "o" : Qt.createComponent("ObjMain.qml").createObject(parentObject) })
}
- Use esta función en su código:
// fragment of the Uimain.qml file
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.RightButton | Qt.LeftButton
onClicked: {
if (mouse.button == Qt.RightButton) {
expanded = !expanded
} else {
createNewObject(object)
}
}
}