2
2
// Copyright (c) JupyterLite Contributors
3
3
// Distributed under the terms of the Modified BSD License.
4
4
5
+ import { expose } from 'comlink' ;
6
+
7
+ import {
8
+ DriveFS ,
9
+ DriveFSEmscriptenNodeOps ,
10
+ IEmscriptenFSNode ,
11
+ IStats
12
+ } from '@jupyterlite/contents' ;
13
+
5
14
declare function createXeusModule ( options : any ) : any ;
6
15
7
16
globalThis . Module = { } ;
8
17
18
+ // TODO Remove this. This is to ensure we always perform node ops on Nodes and
19
+ // not Streams, but why is it needed??? Why do we get Streams and not Nodes from
20
+ // emscripten in the case of xeus-python???
21
+ class StreamNodeOps extends DriveFSEmscriptenNodeOps {
22
+ private getNode ( nodeOrStream : any ) {
23
+ if ( nodeOrStream [ 'node' ] ) {
24
+ return nodeOrStream [ 'node' ] ;
25
+ }
26
+ return nodeOrStream ;
27
+ }
28
+
29
+ lookup ( parent : IEmscriptenFSNode , name : string ) : IEmscriptenFSNode {
30
+ return super . lookup ( this . getNode ( parent ) , name ) ;
31
+ }
32
+
33
+ getattr ( node : IEmscriptenFSNode ) : IStats {
34
+ return super . getattr ( this . getNode ( node ) ) ;
35
+ }
36
+
37
+ setattr ( node : IEmscriptenFSNode , attr : IStats ) : void {
38
+ super . setattr ( this . getNode ( node ) , attr ) ;
39
+ }
40
+
41
+ mknod (
42
+ parent : IEmscriptenFSNode ,
43
+ name : string ,
44
+ mode : number ,
45
+ dev : any
46
+ ) : IEmscriptenFSNode {
47
+ return super . mknod ( this . getNode ( parent ) , name , mode , dev ) ;
48
+ }
49
+
50
+ rename (
51
+ oldNode : IEmscriptenFSNode ,
52
+ newDir : IEmscriptenFSNode ,
53
+ newName : string
54
+ ) : void {
55
+ super . rename ( this . getNode ( oldNode ) , this . getNode ( newDir ) , newName ) ;
56
+ }
57
+
58
+ rmdir ( parent : IEmscriptenFSNode , name : string ) : void {
59
+ super . rmdir ( this . getNode ( parent ) , name ) ;
60
+ }
61
+
62
+ readdir ( node : IEmscriptenFSNode ) : string [ ] {
63
+ return super . readdir ( this . getNode ( node ) ) ;
64
+ }
65
+ }
66
+
67
+ // TODO Remove this when we don't need StreamNodeOps anymore
68
+ class LoggingDrive extends DriveFS {
69
+ constructor ( options : DriveFS . IOptions ) {
70
+ super ( options ) ;
71
+
72
+ this . node_ops = new StreamNodeOps ( this ) ;
73
+ }
74
+ }
75
+
9
76
// when a toplevel cell uses an await, the cell is implicitly
10
77
// wrapped in a async function. Since the webloop - eventloop
11
78
// implementation does not support `eventloop.run_until_complete(f)`
@@ -17,27 +84,7 @@ globalThis.Module = {};
17
84
globalThis . toplevel_promise = null ;
18
85
globalThis . toplevel_promise_py_proxy = null ;
19
86
20
- // We alias self to ctx and give it our newly created type
21
- const ctx : Worker = self as any ;
22
- let raw_xkernel : any ;
23
- let raw_xserver : any ;
24
-
25
- async function waitRunDependency ( ) {
26
- const promise = new Promise ( ( r : any ) => {
27
- globalThis . Module . monitorRunDependencies = ( n : number ) => {
28
- if ( n === 0 ) {
29
- console . log ( 'all `RunDependencies` loaded' ) ;
30
- r ( ) ;
31
- }
32
- } ;
33
- } ) ;
34
- // If there are no pending dependencies left, monitorRunDependencies will
35
- // never be called. Since we can't check the number of dependencies,
36
- // manually trigger a call.
37
- globalThis . Module . addRunDependency ( 'dummy' ) ;
38
- globalThis . Module . removeRunDependency ( 'dummy' ) ;
39
- return promise ;
40
- }
87
+ let resolveInputReply : any ;
41
88
42
89
async function get_stdin ( ) {
43
90
const replyPromise = new Promise ( resolve => {
@@ -46,49 +93,107 @@ async function get_stdin() {
46
93
return replyPromise ;
47
94
}
48
95
49
- // eslint-disable-next-line
50
- // @ts -ignore: breaks typedoc
51
- ctx . get_stdin = get_stdin ;
96
+ ( self as any ) . get_stdin = get_stdin ;
52
97
53
- let resolveInputReply : any ;
98
+ class XeusPythonKernel {
99
+ constructor ( ) {
100
+ this . _ready = new Promise ( resolve => {
101
+ this . initialize ( resolve ) ;
102
+ } ) ;
103
+ }
54
104
55
- async function load ( ) {
56
- const options : any = { } ;
105
+ async ready ( ) : Promise < void > {
106
+ return await this . _ready ;
107
+ }
57
108
58
- importScripts ( './xpython_wasm.js' ) ;
109
+ mount ( driveName : string , mountpoint : string , baseUrl : string ) : void {
110
+ const { FS , PATH , ERRNO_CODES } = globalThis . Module ;
111
+
112
+ this . _drive = new LoggingDrive ( {
113
+ FS ,
114
+ PATH ,
115
+ ERRNO_CODES ,
116
+ baseUrl,
117
+ driveName,
118
+ mountpoint
119
+ } ) ;
120
+
121
+ FS . mkdir ( mountpoint ) ;
122
+ FS . mount ( this . _drive , { } , mountpoint ) ;
123
+ FS . chdir ( mountpoint ) ;
124
+ }
59
125
60
- globalThis . Module = await createXeusModule ( options ) ;
126
+ cd ( path : string ) {
127
+ if ( ! path ) {
128
+ return ;
129
+ }
61
130
62
- importScripts ( './python_data.js' ) ;
131
+ globalThis . Module . FS . chdir ( path ) ;
132
+ }
63
133
64
- await waitRunDependency ( ) ;
65
- raw_xkernel = new globalThis . Module . xkernel ( ) ;
66
- raw_xserver = raw_xkernel . get_server ( ) ;
67
- raw_xkernel ! . start ( ) ;
68
- }
134
+ async processMessage ( event : any ) : Promise < void > {
135
+ await this . _ready ;
136
+
137
+ if (
138
+ globalThis . toplevel_promise !== null &&
139
+ globalThis . toplevel_promise_py_proxy !== null
140
+ ) {
141
+ await globalThis . toplevel_promise ;
142
+ globalThis . toplevel_promise_py_proxy . delete ( ) ;
143
+ globalThis . toplevel_promise_py_proxy = null ;
144
+ globalThis . toplevel_promise = null ;
145
+ }
146
+
147
+ const msg_type = event . msg . header . msg_type ;
148
+
149
+ if ( msg_type === 'input_reply' ) {
150
+ resolveInputReply ( event . msg ) ;
151
+ } else {
152
+ this . _raw_xserver . notify_listener ( event . msg ) ;
153
+ }
154
+ }
69
155
70
- const loadCppModulePromise = load ( ) ;
156
+ private async initialize ( resolve : ( ) => void ) {
157
+ importScripts ( './xpython_wasm.js' ) ;
71
158
72
- ctx . onmessage = async ( event : MessageEvent ) : Promise < void > => {
73
- await loadCppModulePromise ;
159
+ globalThis . Module = await createXeusModule ( { } ) ;
74
160
75
- if (
76
- globalThis . toplevel_promise !== null &&
77
- globalThis . toplevel_promise_py_proxy !== null
78
- ) {
79
- await globalThis . toplevel_promise ;
80
- globalThis . toplevel_promise_py_proxy . delete ( ) ;
81
- globalThis . toplevel_promise_py_proxy = null ;
82
- globalThis . toplevel_promise = null ;
83
- }
161
+ importScripts ( './python_data.js' ) ;
162
+
163
+ await this . waitRunDependency ( ) ;
164
+
165
+ this . _raw_xkernel = new globalThis . Module . xkernel ( ) ;
166
+ this . _raw_xserver = this . _raw_xkernel . get_server ( ) ;
167
+
168
+ if ( ! this . _raw_xkernel ) {
169
+ console . error ( 'Failed to start kernel!' ) ;
170
+ }
84
171
85
- const data = event . data ;
86
- const msg = data . msg ;
87
- const msg_type = msg . header . msg_type ;
172
+ this . _raw_xkernel . start ( ) ;
88
173
89
- if ( msg_type === 'input_reply' ) {
90
- resolveInputReply ( msg ) ;
91
- } else {
92
- raw_xserver ! . notify_listener ( msg ) ;
174
+ resolve ( ) ;
93
175
}
94
- } ;
176
+
177
+ private async waitRunDependency ( ) {
178
+ const promise = new Promise < void > ( resolve => {
179
+ globalThis . Module . monitorRunDependencies = ( n : number ) => {
180
+ if ( n === 0 ) {
181
+ resolve ( ) ;
182
+ }
183
+ } ;
184
+ } ) ;
185
+ // If there are no pending dependencies left, monitorRunDependencies will
186
+ // never be called. Since we can't check the number of dependencies,
187
+ // manually trigger a call.
188
+ globalThis . Module . addRunDependency ( 'dummy' ) ;
189
+ globalThis . Module . removeRunDependency ( 'dummy' ) ;
190
+ return promise ;
191
+ }
192
+
193
+ private _raw_xkernel : any ;
194
+ private _raw_xserver : any ;
195
+ private _drive : DriveFS | null = null ;
196
+ private _ready : PromiseLike < void > ;
197
+ }
198
+
199
+ expose ( new XeusPythonKernel ( ) ) ;
0 commit comments