Remodel server-side object representation
Problem statement
The current structure of our Wrapland::Server classes is very complicated and difficult to maintain.
We have for Globals and Resources (here the OutputInterface:Global
example):
Global <-- Private
^ ^
| |
| |
OutputInterface <-- Private
That is the typical inheritance optimizing d-pointer scheme used in Qt. But here, for Globals and Resources the structure does not feel quite right. Possible reasons are:
- Wiring up the libwayland wl_globals and wl_resources is complicated because of the inheritance. There is a virtual member function pointer for binding to the Wayland objects.
- Some Global children track the bound resources, others not. It always has to be put in the child class and it is difficult to understand for novice contributors why sometimes it is not necessary (bound resources are destroyed automatically on global destroy and the tracking is only for access while alive and bound).
- For each resource we create an XyzInterface class with its Resource superclass + a Private class with its Resource::Private superclass. That is complicated and seems wasteful for wl_resources since each window could have hundreds of them at the same time.
- The name XyzInterface is tiring long and misleading because an interface class in C++ is something different.
- A Global when bound is also represented by a wl_resource. But only dependent wl_resources created through wl_globals are in the "Resource" class. That naming is confusing.
- The wl_resource of Resources are created directly in the Resource class but the wl_resource of a Global is supposed to be created in the child-class.
- Some classes have grown immensely over time and connect to all kind of different objects. For example as a Global the SeatInterface and as a Resource the SurfaceInterface.
Solution
- For (1), (3): The data in the Global/Resource parent class should be composited into the "Interfaces" instead of inherited.
- For (2): Always track bound Globals in a common implementation.
- For (3): There will be a class for wl_resource but this one does not inherit QObject. See below.
- For (4): Rename all
Server::XyzInterface
just toServer::Xyz
. We have namespaces for a reason. - For (5): Rename exported Resource to something else or remove the need to have super-classes on exported wl_resource wrappers besides QObject. See below for second alternative.
- For (6): See below.
- For (7): Move functionality from grown wl_object classes into the interacting classes. For example have all the Pointer logic in the PointerInterface class and not in the SeatInterface class.
wl_object representation
Let's first set one axiomatic requirement and see if it holds up: (A1) The boundedness of a wl_global and the existence of a wl_resource shall not be emitted by signals/slots.
Instead:
Globals
- On global-creation an internal Server::Wayland::Global wl_global wrapper must be created by the compositor-facing Server::Xyz class. This common code could be put in an interface class (GlobalInterface?).
- On bind the wl_resource shall be directly created and tracked in the Wayland::Global class (probably through with a wrapper Wayland::Resource saving the client information).
- If other wl_objects are created through this global they are tracked in Wayland::Global too (in the client-specific wrapper).
- On unbind only the data internal to Wayland::Global is affected and relevant tracked wl_objects are informed. Here additional functions and checks can be performed.
(Global-dependent) Resources
-
As said they are tracked in the Wayland::Resource class of the global they were created from.
-
It could make sense for them to be wrapped into the very same Wayland::Resource class their global is.
-
If the compositor wants to manipulate or receive information from one of these resources directly it can access them through a proxy class created by the Global. This interface could directly inherit QObject. The flow would be like that:
-
Wayland::Resource res is created by client at global Server::XyzManager : Server::Global.
-
XyzManager emits signal about the creation received by compositor. This signal either directly has as payload a compositor-facing proxy object for the compositor to interact with or there is a direct way the compositor can later retrieve such a proxy through the Global, for example the surface-pointer combination a pointer-lock was created for (solves in particular problem (7)). Probably for some protocol object like a pointer lock this makes more sense than for example for a xdg-toplevel that always is tracked internally in the compositor window stack by something.
-
Compositor decides at some point to interact with the resource res. It calls a factory method in global XyzManager or uses the directly created proxy in the payload.
-
XyzManager creates a proxy Xyz : QObject with a d-ptr Private having res in a member field. The Private also has all the event/request logic and connects to the destroy/unbind logic of other proxies it depends on. For example the pointer-lock depending on a pointer and surface proxy connects to these proxies' destroy/unbind signals.
-
Cleanup: When the compositor deletes its proxy the resource goes back into the lazy-load state.
When the client destroys its proxy the resource goes in a defunct state and the compositor proxy will be informed by a signal from the creating Global. Events that the compositor sends after that to the resource go void. The compositor should react by destroying its proxy.
-
Approach
It should be tried to introduce these remodeled classes with current ones still being in use and make them interact with them like the previous classes. A first candidate are global-only classes. For example OutputInterface.
See ticket #15 (closed) for that.
If this is not possible a minimal set of functional classes needs to be created on a feature branch and from there extended until all classes have been remodeled.