Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit9165dcf

Browse files
authored
Merge pull requestalexmojaki#285 from alexmojaki/channel
Safari support
2 parents86d3d19 +8a42ef7 commit9165dcf

File tree

12 files changed

+138
-80
lines changed

12 files changed

+138
-80
lines changed

‎core/checker.py‎

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
importast
22
importinspect
33
importlogging
4+
importtime
45
fromcollectionsimportdefaultdict
56

67
fromcore.exercisesimportassert_equal
@@ -43,15 +44,14 @@ def reset(self):
4344
self.console.locals.update(assert_equal=assert_equal)
4445

4546
defnon_str_input(self):
46-
whileTrue:
47-
pass# wait for the interrupt
47+
raiseKeyboardInterrupt
4848

4949

5050
runner=FullRunner()
5151

5252

5353
@catch_internal_errors
54-
defcheck_entry(entry,input_callback,output_callback):
54+
defcheck_entry(entry,input_callback,output_callback,sleep_callback=time.sleep):
5555
result=dict(
5656
passed=False,
5757
messages=[],
@@ -77,7 +77,11 @@ def full_output_callback(data):
7777
data["parts"]=parts
7878
returnoutput_callback(data)
7979

80-
runner.set_combined_callbacks(output=full_output_callback,input=input_callback)
80+
runner.set_combined_callbacks(
81+
output=full_output_callback,
82+
input=input_callback,
83+
sleep=sleep_callback,
84+
)
8185
runner.question_wizard=entry.get("question_wizard")
8286
runner.input_nodes=defaultdict(list)
8387

‎core/runner/runner.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
frompython_runnerimportPatchedStdinRunner
1+
frompython_runnerimportPatchedStdinRunner,PatchedSleepRunner
22

33

4-
classEnhancedRunner(PatchedStdinRunner):
4+
classEnhancedRunner(PatchedStdinRunner,PatchedSleepRunner):
55
defexecute(self,code_obj,source_code,mode=None):
66
ifmode=="snoop":
77
fromcore.runner.snoopimportexec_snoop

‎frontend/.gitignore‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,6 @@ yarn-error.log*
2626
chapters.json
2727
python_core.tar
2828
public/birdseye/
29+
30+
public/service-worker.js
31+
public/service-worker.js.map

‎frontend/package-lock.json‎

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more aboutcustomizing how changed files appear on GitHub.

‎frontend/package.json‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@
3535
"redux":"^4.0.5",
3636
"redux-logger":"^3.0.6",
3737
"redux-thunk":"^2.3.0",
38-
"sass":"^1.32.8"
38+
"sass":"^1.32.8",
39+
"sync-message":"0.0.3"
3940
},
4041
"scripts": {
4142
"start":"craco start",

‎frontend/src/RunCode.js‎

Lines changed: 79 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -18,20 +18,35 @@ import localforage from "localforage";
1818
import{animateScroll}from"react-scroll";
1919
importReactfrom"react";
2020
import*asSentryfrom"@sentry/react";
21+
import{makeAtomicsChannel,makeServiceWorkerChannel}from"sync-message";
2122

22-
constworkerWrapper=Comlink.wrap(newWorker());
23+
letworker,workerWrapper;
2324

24-
letinputTextArray,inputMetaArray,interruptBuffer=null;
25-
if(typeofSharedArrayBuffer=="undefined"){
26-
inputTextArray=null;
27-
inputMetaArray=null;
28-
}else{
29-
inputTextArray=newUint8Array(newSharedArrayBuffer(128*1024));
30-
inputMetaArray=newInt32Array(newSharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT*2));
31-
interruptBuffer=newInt32Array(newSharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT*1));
25+
functioninitWorker(){
26+
worker=newWorker();
27+
workerWrapper=Comlink.wrap(worker);
3228
}
3329

34-
constencoder=newTextEncoder();
30+
initWorker();
31+
32+
constchannelPromise=(async()=>{
33+
if(typeofSharedArrayBuffer!=="undefined"){
34+
returnmakeAtomicsChannel();
35+
}else{
36+
awaitnavigator.serviceWorker.register("./service-worker.js");
37+
constresult=awaitmakeServiceWorkerChannel({timeout:1000});
38+
if(!result){
39+
// TODO what if this doesn't work?
40+
window.location.reload();
41+
}
42+
returnresult;
43+
}
44+
})();
45+
46+
letinterruptBuffer=null;
47+
if(typeofSharedArrayBuffer!="undefined"){
48+
interruptBuffer=newInt32Array(newSharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT*1));
49+
}
3550

3651
exportconstterminalRef=React.createRef();
3752

@@ -40,25 +55,35 @@ let pendingOutput = [];
4055

4156
localforage.config({name:"birdseye",storeName:"birdseye"});
4257

43-
functioninputCallback(){
44-
awaitingInput=true;
58+
functioninputCallback(messageId){
59+
awaitingInput=messageId;
4560
bookSetState("processing",false);
4661
terminalRef.current.focusTerminal();
4762
}
4863

4964
exportletinterrupt=()=>{
5065
};
5166

67+
letfinishedLastRun=Promise.resolve();
68+
letfinishedLastRunResolve=()=>{};
69+
5270
exportconstrunCode=async({code, source})=>{
5371
constshell=source==="shell";
5472
if(shell){
5573
if(awaitingInput){
74+
constmessageId=awaitingInput;
5675
awaitingInput=false;
57-
writeInput(code);
76+
(awaitchannelPromise).writeInput({text:code},messageId);
5877
bookSetState("processing",true);
5978
return;
6079
}
6180
}else{
81+
if(bookState.running){
82+
interrupt();
83+
awaitfinishedLastRun;
84+
}
85+
finishedLastRun=newPromise(r=>finishedLastRunResolve=r);
86+
6287
terminalRef.current.clearStdout();
6388
}
6489

@@ -84,14 +109,39 @@ export const runCode = async ({code, source}) => {
84109
expected_output:questionWizard.expectedOutput,
85110
};
86111

87-
interrupt();
88-
interruptBuffer=newInt32Array(newSharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT*1));
89112
letinterrupted=false;
90-
interrupt=()=>{
91-
interruptBuffer[0]=2;
113+
letinterruptResolver;
114+
constinterruptPromise=newPromise(r=>interruptResolver=r);
115+
interrupt=async()=>{
116+
if(awaitingInput){
117+
constmessageId=awaitingInput;
118+
awaitingInput=false;
119+
(awaitchannelPromise).writeInput({interrupted:true},messageId);
120+
}else{
121+
doInterrupt();
122+
}
92123
interrupted=true;
93124
}
94125

126+
letdoInterrupt;
127+
if(typeofSharedArrayBuffer==="undefined"){
128+
doInterrupt=()=>{
129+
worker.terminate();
130+
initWorker();
131+
interruptResolver({
132+
interrupted:true,
133+
error:null,
134+
passed:false,
135+
messages:[],
136+
});
137+
}
138+
}else{
139+
interruptBuffer=newInt32Array(newSharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT*1));
140+
doInterrupt=()=>{
141+
interruptBuffer[0]=2;
142+
}
143+
}
144+
95145
consthasPrediction=currentStep().prediction.choices;
96146

97147
functionoutputCallback(output_parts){
@@ -108,14 +158,16 @@ export const runCode = async ({code, source}) => {
108158
}
109159
}
110160

111-
constdata=awaitworkerWrapper.runCode(
112-
entry,
113-
inputTextArray,
114-
inputMetaArray,
115-
interruptBuffer,
116-
Comlink.proxy(outputCallback),
117-
Comlink.proxy(inputCallback),
118-
);
161+
constdata=awaitPromise.race([
162+
interruptPromise,
163+
workerWrapper.runCode(
164+
entry,
165+
(awaitchannelPromise).channel,
166+
interruptBuffer,
167+
Comlink.proxy(outputCallback),
168+
Comlink.proxy(inputCallback),
169+
),
170+
]);
119171

120172
awaitingInput=false;
121173

@@ -169,6 +221,8 @@ export const runCode = async ({code, source}) => {
169221
terminalRef.current.focusTerminal();
170222
}
171223

224+
finishedLastRunResolve();
225+
172226
if(isProduction){
173227
databaseRequest("POST",{
174228
entry,
@@ -193,17 +247,6 @@ document.addEventListener('keydown', function (e) {
193247
}
194248
});
195249

196-
constwriteInput=(string)=>{
197-
constbytes=encoder.encode(string);
198-
if(bytes.length>inputTextArray.length){
199-
throw"Input is too long";// TODO
200-
}
201-
inputTextArray.set(bytes,0);// TODO ensure no race conditions
202-
Atomics.store(inputMetaArray,0,bytes.length);
203-
Atomics.store(inputMetaArray,1,1);
204-
Atomics.notify(inputMetaArray,1);
205-
}
206-
207250
functionshowOutputParts(output_parts){
208251
constterminal=terminalRef.current;
209252
terminal.pushToStdout(output_parts);

‎frontend/src/Worker.js‎

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import*asComlinkfrom'comlink';
66
importpythonCoreUrlfrom"./python_core.tar.load_by_url"
77
importloadPythonStringfrom"!!raw-loader!./load.py"
8+
import{readChannel,syncSleep,uuidv4}from"sync-message";
89

910
asyncfunctiongetPackageBuffer(){
1011
constresponse=awaitfetch(pythonCoreUrl);
@@ -62,29 +63,22 @@ const toObject = (x) => {
6263
}
6364
}
6465

65-
constdecoder=newTextDecoder();
66-
67-
asyncfunctionrunCode(entry,inputTextArray,inputMetaArray,interruptBuffer,outputCallback,inputCallback){
66+
asyncfunctionrunCode(entry,channel,interruptBuffer,outputCallback,inputCallback){
6867
awaitpyodideReadyPromise;
6968

7069
constfullInputCallback=(data)=>{
71-
inputCallback(toObject(data));
72-
while(true){
73-
if(Atomics.wait(inputMetaArray,1,0,50)==="timed-out"){
74-
if(interruptBuffer[0]===2){
75-
returnnull;
76-
}
77-
}else{
78-
break
79-
}
70+
constmessageId=uuidv4();
71+
inputCallback(messageId,toObject(data));
72+
constresult=readChannel(channel,messageId).text;
73+
if(result==null){
74+
returnnull;
8075
}
81-
Atomics.store(inputMetaArray,1,0);
82-
constsize=Atomics.exchange(inputMetaArray,0,0);
83-
constbytes=inputTextArray.slice(0,size);
84-
returndecoder.decode(bytes)+"\n";
76+
returnresult+"\n";
8577
}
8678

87-
pyodide._module.setInterruptBuffer(interruptBuffer);
79+
if(interruptBuffer){
80+
pyodide._module.setInterruptBuffer(interruptBuffer);
81+
}
8882

8983
try{
9084
awaitinstall_imports(entry.input);
@@ -96,7 +90,12 @@ async function runCode(entry, inputTextArray, inputMetaArray, interruptBuffer, o
9690
constfullOutputCallback=(data)=>{
9791
outputPromise=outputCallback(toObject(data).parts);
9892
};
99-
constresult=check_entry(entry,fullInputCallback,fullOutputCallback);
93+
94+
functionsleepCallback(data){
95+
syncSleep(toObject(data).seconds*1000,channel);
96+
}
97+
98+
constresult=check_entry(entry,fullInputCallback,fullOutputCallback,sleepCallback);
10099
awaitoutputPromise;
101100
returntoObject(result);
102101
}

‎frontend/src/index.js‎

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,6 @@ import {Provider} from "react-redux";
55
import{store}from"./store";
66

77
ReactDOM.render(
8-
typeofSharedArrayBuffer=="undefined" ?
9-
<divclassName="container">
10-
<h1>Browser not supported</h1>
11-
<p>Sorry, futurecoder doesn't work yet on this browser. The following browsers should work:</p>
12-
<ul>
13-
<li>Chrome</li>
14-
<li>Firefox</li>
15-
<li>Edge</li>
16-
<li>Opera</li>
17-
</ul>
18-
<p>Mobile devices and Safari are currently not supported.</p>
19-
</div>
20-
:
218
<Providerstore={store}>
229
<App/>
2310
</Provider>,

‎frontend/src/service-worker.js‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import{serviceWorkerFetchListener}from"sync-message";
2+
3+
console.log(self.__WB_MANIFEST);
4+
5+
constfetchListener=serviceWorkerFetchListener();
6+
7+
addEventListener('fetch',fetchListener);
8+
9+
addEventListener('install',function(e){
10+
e.waitUntil(self.skipWaiting());
11+
});
12+
13+
addEventListener('activate',function(e){
14+
e.waitUntil(self.clients.claim());
15+
});

‎frontend/src/shell/Terminal.jsx‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,9 @@ export default class Terminal extends Component {
172172
handleInput(event){
173173
switch(event.key){
174174
case'Enter':
175-
this.processCommand();
175+
if(!(event.ctrlKey||event.metaKey)){
176+
this.processCommand();
177+
}
176178
break;
177179
case'ArrowUp':
178180
this.scrollHistory('up');

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp