pi module documentation

The pi (Porting Interfaces) module contains all interfaces for everything that must be done in a platform-specific way, which thus does not belong in core. Displaying text in a web page, for example, requires usage of some font engine and painting in some sort of windowing system, and it is up to the platform to decide how to do this. All interfaces must be implemented when porting Opera to a new platform; however, some of them may have been implemented in a cross-platform manner inside core, and a platform may choose to use or not use them. For example, there is a platform-independent host name resolver in core (implementation of OpHostResolver) that acts as a DNS client using OpSocket. There is also a concept called GOGI, which implements the window management and painting classes (OpPainter, OpView, OpBitmap, etc.) in a platform-independent way, so that all that the actual platform needs to implement is a frame buffer interface and some basic input events. GOGI is ideal for rapid porting and/or when there is little benefit (performance, etc.) in making native implementations of those classes.

The porting interfaces have developed during several years, and have been adjusted and appended so that Opera has become more and more portable.

API documentation

Go to the API documentation, generated with Doxygen.

History

The porting interfaces were introduced in 2001 as a directory in the Opera CVS module (Opera/Porting_Interfaces/). When core was modularized a couple of years later, it was moved to the pi module.

Prior to the introduction of porting interfaces, core had a lot of ifdefs for each platform supported. Among other things, each platform had their own version of VisualDevice, a class currently in the display module, controlled by ifdefs. The same was true for the Comm class in the url module. This sort of worked (although it became really messy and difficult to maintain) when there were less than 5 platforms to support.

Class categories

The pi module consists of three main categories of classes:

What's in here?

This module contains interfaces that core requires the platform to implement, in order to work.

The porting interfaces cover the following basic platform dependencies that Opera has, such as:

This module also contains a Pike script that can be used to create stub implementations of all the interfaces that are defined in this module. This may save an implementor some amount of boring work when porting Opera to a new platform, or when implementing a new interface. The script is called make_stubs.pike

What's NOT in here?

APIs for user interface and product code are not in this module. Such APIs belong in the windowcommander module. See the chapter about The burger model.

The burger model

(user interface and product code)

windowcommander module

core proper modules

pi module

(platform code)

The burger model

From a core-perspective[1], if we imagine Opera as a 3-layer hamburger, the porting interfaces form the bottom bun of the burger. This is the interface towards the platforms. The burger's middle part, the meaty burger patty is Opera's core, and the top bun is the interface towards the different user interfaces and general product control. That part is taken care of by the windowcommander module. The bottom layer varies with platform (Linux, Symbian, etc.), the middle layer varies with feature settings (mouse support, ECMA Script support, etc.), the top layer varies with product (desktop, gogi).

Ownership-wise it goes like this: The product/UI owns core through windowcommander (and hardcore for initialization and shutdown), core owns the platform through pi.

Direction of requests or commands (do this!) in this model is downwards. The product/UI requests/commands core to do something via windowcommander (e.g. go to URL), core requests/commands the platform to do something via pi (e.g. connect to IP address).

Direction of notifications (this happened) in this model is upwards. The platform notifies core about events via pi (e.g. data received on socket), core notifies the product/UI about events via windowcommander (e.g. 70% of document loaded). This is implemented with methods in listener classes.

It follows from this model that core should never command the user interface; instead it should notify it about events. That is, core does not tell the user interface: Display a top-aligned bar with red Tahoma 12pt font on white background with the text '70% lastet'!. No, it tells the user interface Just so you know, 70% of the document has been loaded. The the user interface code decides how to convey this information to the user (if at all).

[1] The burger model is useful to describe how it looks from core's point of view. Outside of core, on the platform or product/UI side (if there is such a distinction), platform and product/UI code may meet, making it look more like a hot dog model, or even French hot dog model. This is probably the case for gogi. The burger model may be somewhat obscured to the gogi client, as gogi takes care of a lot of the communication with (and implementation of) pi and windowcommander, so that the platform/product implementor won't have to bother so much about classes like e.g. OpWindowCommander and OpPainter. Having said that, it probably makes sense to retain as much as possible of the burger model idea on the platform / product side too.

Design goals and scope

The basic design goal is that no platform dependent code should remain in core. This code should be abstracted and broken out into proper porting interfaces. This way core code doesn't have to be infested with platform #ifdefs.

What is considered proper is up for discussion in each case. One needs to determine if an interface is more of a product/user interface, than a platform interface. If it is, it should most likely go into the top bun of the burger (see chapter about The burger model), i.e. the windowcommander module. The pi module is and shall remain Opera core's only means of interacting with the underlying (operating) system (and windowing system). It shall not contain anything else.

All interfaces defined in this module shall be used by core in some way or another; or they don't belong here. That is, interfaces that indeed may be needed on several platforms from e.g. cross-platform user interface code (such as QUICK), but that are not used in core code, do not belong in the pi module. Such interfaces could belong in mantle, or, in the case of the desktop version of Opera, perhaps the desktop_pi module.

Another design goal is to keep the interface as small as possible. We have no intentions of building a full, general-purpose library; we only want whatever is needed to port Opera to any platform. This is especially important because in principle, most of the interfaces have to be implemented on every platform, from the smallest web-capable earring or mobile phone, to the largest desktop or web-capable meaning-of-life computing machine.

It is also important to choose a good level of abstraction; the interface should be as generic as necessary and reasonable, but not necessarily as generic as possible. (example: Logitech mouse, mouse, pointing device, input thingie, or body extension?)

Under no circumstances should the platform implementation of porting interfaces have to access global objects in core.[1]

[1] This is currently not possible for a platform implementation to achieve. To signal keyboard input, it needs to access g_input_manager in the inputmanager module. A lot of platform implementations also access g_prefsManager in the prefs module.

Data types and macros

We should use as few data types as possible in the pi module, to keep dependencies between core code and platform code at a minimum. The pi module is however not entirely self-contained; some essential data types and macros are provided by the hardcore and util modules.

Types not defined in the pi module allowed in pi interfaces and listeners:

Allowed core macros for the interface itself and the platform implementation:

Regarded as design flaws, the following additional types are currently used in pi:

API changes

Since practically everything in the pi module actually is some sort of interface used or implemented by other modules, changing it will normally affect other modules. For core modules, the regular capability system is used to deal with API changes in pi. This way, upgrading or downgrading pi or the module that a change affects, will work smoothly and cause no compilation or runtime errors (if the capability system is used properly). Changes in pi that affect platform code will also be announced with a capability define, but since platform code cannot announce capabilities, this only goes one way, meaning that a change in pi will not wait for the platforms to adapt. Platform code can retain backwards-compatiblity with older pi module versions by checking for a capability announced by pi, but the platform code is not forwards-compatible with future module releases.

Implementation details

Each interface is a C++ class with only pure virtual members[1]. Non-const pass-by-reference is implemented with pointers[2], for code readability reasons. The idea is that it is more obvious that parameter data is to be modified by the method if it is passed as a pointer instead of as a reference. Note that this does not apply to objects passed as const; we want to be able to do e.g. miff->Bar(OpRect(17,17,4711, 4711)) (where Bar is declared as virtual void Bar(const OpRect&)).

Each class name should start with the Op prefix. A main API class along with its related listener classes (if any) should be put in a header file with the same file name as the main class, with .h appended. Capitalization in the class name is retained in the file name. (Class OpFoo is found in file OpFoo.h)

A class name should consist of Op followed by a noun or a compound noun.

A method name in a request class should resemble a command, typically a clause starting with a verb in its imperative mood, optionally followed by an object. Example: Eat the burger! → EatBurger()

A method name in a listener class should start with On followed by a clause in passive voice (only the participle), pretty much like a newspaper headline. Example: The burger was eaten by a horse → OnBurgerEatenByHorse()

Unless instances of a class are created by calling some method on a manager class or owner object (e.g. OpFontManager::CreateFont()), the class has a static Create() method, which creates (and initializes) an instance of the class. It is implemented on the platform side, typically along with the rest of the abstract class re-implementation.

[1] We have some members still remaining as non-pure virtual, because people have historically taken shortcuts. Work is in progress to fix that.

[2] Normally true, and this is the rule to follow when adding new methods or parameters to the interfaces. However, we still have some methods that pass by reference using '&' instead of '*'.

Coding style

Tabs (4 spaces wide) are used for indentation.

Each class should be prefixed by doxygen documentation, on this format:

/** @short <short description of class>
 *
 * <Additional information, if necessary. May span over several lines if
 * necessary>
 */
class OpFoo
{
public:
    <...>
... and each method should be prefixed like this:
    /** <short description of what the method does, starting with an verb in its imperative mood>
     *
     * <More detailed description of what the method does, if that is
     * necessary. May span over several lines>
     *
     * @param a <description of the 'a' parameter, which may span over
     * several lines>
     * @param hest <description of the 'hest' parameter>
     *
     * @return <possible return values>
     */
    virtual OP_STATUS DoStuff(const uni_char* a, int hest) = 0;

    <...>
};

The width of comment lines should generally not exceed 80 characters (where each tab counts as 4 characters), but the short description part of class or method documentation may, unless it is ridiculously long (but then it's not really a short description, is it?).

Memory handling

Used OOM policies

The pi module normally signals OOM via OP_STATUS. In cases where the surrounding core code relies heavily on TRAP/LEAVE, that mechanism is used instead.

Who is handling OOM?

The pi module does not need to deal much with OOM handling on its own, since there's almost no code in there. The module initialization sequence PiModule::InitL() may run out of memory, and if it happens, it will propagate that via LEAVE.

OOM is signalled to callers of methods in the porting interface classes. Methods in porting interface listener classes may never signal OOM. In other words, platform code may report OOM to core code, but not vice-versa.

Description of flow

Core calls a porting interface method. If the method signals OOM, it is up to the caller to deal with that.

Heap memory usage

The module itself doesn't use heap memory at all, except for whatever is required by the classes initialized in PiModule::InitL(), and that shouldn't be much.

The platform code implementing the porting interface may use considerable amounts of heap memory. This is the case especially for OpBitmap. When core creates a new image object, the pixel data is allocated and owned by the implementation of OpBitmap. OpFont may also use a lot of heap memory. OpSocket may have to use some memory for network buffering. OpView may have to use some for double-buffering. There is no way for core to keep track of this kind of memory usage, except for the OpBitmap case, where OpScreenInfo can tell core how much RAM an OpBitmap of a given size would require.

Stack memory usage

The worst case of stack usage is most likely the callbacks from the platform layer back into the Opera core code. One example is the socket code: if sending all data completes immediately, the platform may notify core code about this immediately, and core code may then legally send more data right away. It is the platform implementation's responsibility to limit the recursion depth in this case, and core's responsibility to avoid deep recursions.

Static memory usage

The interfaces use none. Platform implementations may use some.

Caching and freeing memory

There are currently no means of requesting platform implementation to reduce memory usage, other than deleting pi objects.

Freeing memory on exit

PiModule::Destroy() frees the memory. Platform code is responsible for freeing any static memory allocated on their side.

Temp buffers

N/A

Memory tuning

No tuning.

Tests

No tests.

Coverage

Load and display a web page with text, images, plug-ins, borders and scrollbars over HTTP. Print it.

Design choices

Since there is almost no code in this module, one can say that this module deals more with memory information (you're out of memory, to store an image of that size I need to allocate this many bytes) than memory itself.

Suggestions of improvements

We should consider getting better control over the platform implementation's memory usage. Classes that are likely to use a lot of memory, are OpBitmap, OpFont and OpView.