Skip to content

Commit

Permalink
WIP Detail Design
Browse files Browse the repository at this point in the history
  • Loading branch information
Lecrapouille committed Dec 8, 2024
1 parent 5f0553d commit 499b07f
Show file tree
Hide file tree
Showing 14 changed files with 537 additions and 85 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Since this README is not included when importing gdCEF from the [Godot Asset Lib
- [Installation](addons/gdcef/doc/installation.md)
- [Releases](https://github.com/Lecrapouille/gdcef/releases)
- [API](addons/gdcef/doc/API.md)
- [Architecture](addons/gdcef/doc/architecture.md)
- [Details Design](addons/gdcef/doc/detailsdesign.md)
- [Demos](addons/gdcef/demos/README.md)
- [FAQ](addons/gdcef/README.md#faq)
Expand Down
175 changes: 105 additions & 70 deletions addons/gdcef/doc/architecture.md
Original file line number Diff line number Diff line change
@@ -1,71 +1,106 @@
# Software Architecture.

**WIP: Consider this document as a draft!!!**

This details design document is about [gdcef]
(https://github.com/Lecrapouille/gdcef) implementing a [Chromium Embedded
Framework](https://bitbucket.org/chromiumembedded/cef/wiki/Home) (CEF) native
module for the [Godot editor](https://godotengine.org/) (gdCef).

Inside the `gdcef` folder, two main classes deriving from `godot::Node` have
been created to wrap the CEF C++ API to be usable from Godot scripts. Derived
from Godot Nodes, it allows instances of these classes to be attached inside to
the scene-graph as depicted by the following picture.

![CEFnode](pics/cef.png)

See this
[document](https://docs.godotengine.org/en/stable/classes/class_node.html)
concerning what a Godot Node is.

## Classes Diagram

The following picture depicts the class diagram:

![classdiag](architecture/classes.png)

- `GDCef` implemented in [gdcef/src/gdcef.cpp](gdcef.[ch]pp). Its goal is to
wrap up the initialization phase of CEF, its settings, and the loopback of
messages of CEF subprocesses. This class allows creating `GDBrowserView`
that are attached as child nodes inside the scene-graph.

- `GDBrowserView` implemented in [gdcef/src/gdbrowser.cpp](gdbrowser.[ch]pp).
Its goal is to wrap a browser view allowing to display the web document, to
interact with the user (mouse, keyboard), to load pages, ...

The [gdcef/src/gdlibrary.cpp](gdcef/src/gdlibrary.cpp) allows Godot to
register the two classes. See this document for more information:
https://docs.godotengine.org/en/stable/development/cpp/custom_modules_in_cpp.html

## CEF Secondary process

A [secondary CEF
process](https://bitbucket.org/chromiumembedded/cef/wiki/GeneralUsage.md#markdown-header-separate-sub-process-executable)
is needed when CEF (here, our class `GDCef`) cannot directly access the
`main(int argc, char* argv[])` function of the application. This is mandatory for
its initialization.
<!---
This is, unfortunately, the case since CEF is created as a node
scene-graph but CEF does not come natively inside the Godot engine and
accessing the Godot engine `main` function.
-->
This is, unfortunately, the case since CEF is created as a Node scene-graph
but CEF and its access to Godot Engine's `main` function do not exist
in Godot's source code.

When starting, CEF will fork the application several times into processes
and the forked processes become specialized processes

You have to know that CEF modifies the content of your `argv` and this may mess
up your application if it also parses the command line (you can back it up,
meaning using a `std::vector` to back up `argv` and after CEF init to restore
values in `argv` back). What is "two separated processes" exactly? Just an extra
fork: the main process forks itself and calls the secondary process, which can
fully access it is own main(int argc, char* argv[]). The main constraint is the
path of the secondary process shall be canonic (and this is a pain to get the
real path).

<!---
## Diagram sequence
-->
# Software Architecture

This document details the software architecture of [gdcef](https://github.com/Lecrapouille/gdcef), a native module for the [Godot engine](https://godotengine.org/) that implements the [Chromium Embedded Framework](https://bitbucket.org/chromiumembedded/cef/wiki/Home) (CEF).

## Overview

The gdcef module consists of two main components:

1. A main process that handles CEF initialization and browser management
2. A render process that handles web page rendering and JavaScript execution

## Core Components

### Main Process Classes

Two main classes are exposed to Godot as Nodes:

#### GDCef Class

Implemented in `gdcef/src/gdcef.hpp`, this class serves as the entry point:

- **GDCef**: The entry point class that:
- Initializes CEF and manages its lifecycle
- Handles CEF settings and configuration
- Creates and manages browser instances
- Routes messages between CEF subprocesses

- **GDBrowserView**: Represents a browser instance that:
- Manages web page display and rendering
- Handles user interactions (mouse, keyboard)
- Controls page navigation and JavaScript execution
- Manages audio streaming
- Handles file downloads

These classes are derived from `godot::Node`, allowing them to be integrated into Godot's scene tree:

![CEF node integration](pics/cef.png)

### Render Process

The render process is implemented in a separate executable and handles:

- Web page rendering
- JavaScript execution
- V8 context management
- Communication with the main process

## Communication Flow

### Initialization Sequence

![Init Sequence](architecture/sequence_init.png)

1. GDScript initializes GDCef with configuration
2. CEF initializes and forks required processes
3. The render process starts and initializes its components
4. Browser instances can then be created

### Rendering Sequence

![Paint Sequence](architecture/sequence_paint.png)

1. The render process renders web content
2. Content is painted to an off-screen buffer
3. The buffer is converted to a Godot texture
4. The texture is displayed in the Godot scene

### Download Sequence

![Download Sequence](architecture/sequence_download.png)

1. Download is initiated from browser
2. CEF handles the download process
3. Progress updates are sent to Godot
4. Download completion is signaled

## Class Relationships

The following diagram shows the relationships between the main components:

![Class Diagram](architecture/class_diagram.png)

### Key Relationships

- GDCef creates and manages GDBrowserView instances
- GDBrowserView communicates with CEF browser instances
- The render process communicates with both GDCef and browser instances
- All CEF-related classes implement appropriate CEF interfaces

## Implementation Details

- The module uses CEF's windowless rendering mode for seamless integration with Godot
- Audio is routed through Godot's audio system
- JavaScript integration allows bidirectional communication between Godot and web content
- File downloads are managed through CEF's download handler interface
- Mouse and keyboard events are translated from Godot to CEF format

## Technical Constraints

- CEF requires a separate render process executable
- The render process path must be canonical
- CEF modifies command line arguments during initialization
- Memory management must account for both Godot's reference counting and CEF's reference counting

For API documentation and usage examples, please refer to [API.md](API.md).

Binary file added addons/gdcef/doc/architecture/class_diagram.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
198 changes: 198 additions & 0 deletions addons/gdcef/doc/architecture/class_diagram.puml
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
@startuml

skinparam class {
BackgroundColor White
ArrowColor Black
BorderColor Black
}

package "CEF Framework" #LightBlue {
namespace cef {
interface CefApp {
{abstract}
+GetRenderProcessHandler()
}

interface CefClient {
{abstract}
+GetRenderHandler()
+GetLifeSpanHandler()
+GetLoadHandler()
}

interface CefRenderProcessHandler {
{abstract}
+OnContextCreated()
}

interface CefV8Handler {
{abstract}
+Execute()
}

interface CefRenderHandler {
{abstract}
+GetViewRect()
+OnPaint()
}
}
}

package "Godot Framework" #LightGreen {
namespace godot {
class Object {
{abstract}
}
class Node {
{abstract}
}
class TextureRect {
}

Node --|> Object
TextureRect --|> Node
}
}

package "GDCEF Core" #LightYellow {
class GDCef {
-m_impl: GDCef::Impl*
-m_cef_settings: CefSettings
+initialize(config: Dictionary): bool
+shutdown(): void
+create_browser(): GDBrowserView*
+isAlive(): bool
+get_error(): String
+get_full_version(): String
+get_version_part(entry: int): int
+register_method(godot_object: Object, browser: GDBrowserView, method_name: String): void
}

class "GDCef::Impl" as GDCefImpl {
-m_gdcef: GDCef&
+OnBeforeCommandLineProcessing()
+OnAfterCreated()
+DoClose()
+OnBeforeClose()
+closeAllBrowsers()
}

GDCef --|> godot::Node
GDCef *-- GDCefImpl
GDCefImpl ..|> cef::CefApp
}

package "GDCEF Browser" #LightPink {
class GDBrowserView {
-m_impl: GDBrowserView::Impl*
-m_texture: Ref<ImageTexture>
+init(url: String): int
+load_url(url: String): void
+resize(size: Vector2): void
+execute_javascript(code: String): void
+close(): void
+id(): int
+get_error(): String
+is_valid(): bool
+set_texture(texture: ImageTexture): void
+get_texture(): ImageTexture
+set_zoom_level(level: float): void
+get_title(): String
+get_url(): String
+load_data_uri(html: String, mime_type: String): void
+download_file(url: String): void
+allow_downloads(allow: bool): void
+set_download_folder(path: String): void
+is_loaded(): bool
+reload(): bool
+stop_loading(): void
+copy(): void
+paste(): void
+undo(): void
+redo(): void
+request_html_content(): void
+has_previous_page(): bool
+has_next_page(): bool
+previous_page(): void
+next_page(): void
+set_viewport(x: float, y: float, w: float, h: float): bool
+set_key_pressed(key: int): void
+set_mouse_moved(x: int, y: int): void
+set_mouse_left_click(x: int, y: int): void
+set_mouse_right_click(x: int, y: int): void
+set_mouse_middle_click(x: int, y: int): void
+set_mouse_left_down(x: int, y: int): void
+set_mouse_left_up(x: int, y: int): void
+set_mouse_right_down(x: int, y: int): void
+set_mouse_right_up(x: int, y: int): void
+set_mouse_middle_down(x: int, y: int): void
+set_mouse_middle_up(x: int, y: int): void
+set_mouse_wheel_vertical(delta: int): void
+set_mouse_wheel_horizontal(delta: int): void
+set_muted(mute: bool): bool
+is_muted(): bool
+set_audio_stream(audio: AudioStreamGeneratorPlayback): void
+get_audio_stream(): AudioStreamGeneratorPlayback
+get_pixel_color(x: int, y: int): Color
}

class "GDBrowserView::Impl" as GDBrowserViewImpl {
-m_browser: CefRefPtr<CefBrowser>
-m_texture_rect: TextureRect*
+GetViewRect()
+OnPaint()
+OnLoadEnd()
+OnLoadError()
+OnBeforePopup()
+OnBeforeDownload()
+OnDownloadUpdated()
+OnProcessMessageReceived()
+OnAudioStreamStarted()
+OnAudioStreamPacket()
}

GDBrowserView --|> godot::Node
GDBrowserView *-- GDBrowserViewImpl
GDBrowserView o-- godot::TextureRect
GDBrowserViewImpl ..|> cef::CefClient
GDBrowserViewImpl ..|> cef::CefRenderHandler
}

package "Render Process" #LightGray {
class RenderProcess {
-m_handler: CefRefPtr<GodotMethodHandler>
+GetRenderProcessHandler()
+OnContextCreated()
}

class GodotMethodHandler {
-m_browser: CefRefPtr<CefBrowser>
+Execute()
}

RenderProcess ..|> cef::CefApp
RenderProcess ..|> cef::CefRenderProcessHandler
RenderProcess *-- GodotMethodHandler
GodotMethodHandler ..|> cef::CefV8Handler

' Interactions avec GDCEF Core
RenderProcess ..> GDCef : "Communique avec"
GodotMethodHandler ..> GDCef : "Envoie des messages à"
}

note top of GDCef
Main entry point that initializes CEF
and manages browser instances
end note

note top of GDBrowserView
Represents a browser instance
with its own texture
end note

note top of RenderProcess
Manages the render process
and JavaScript bindings
end note

@enduml
Loading

0 comments on commit 499b07f

Please sign in to comment.