Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions build.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use convert_case::{Case, Casing};
use std::collections::HashSet;
use std::env;
use std::fs;
use std::path::Path;
use std::{env, fs};

use convert_case::{Case, Casing};

// I hate to admit this, but a fair bit of this file was written by chatgpt to speed things up
// and to allow me to continue to procrastinate about learning how to do i/o stuff in rust.
Expand Down
56 changes: 36 additions & 20 deletions enum-field-getter/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
#![doc = include_str!("../README.md")]

use std::collections::{HashMap, HashSet};

use proc_macro::TokenStream;
use proc_macro_error::{abort_call_site, emit_warning, proc_macro_error};
use quote::{format_ident, quote};
use syn::{parse_macro_input, Data, DeriveInput, Fields, Type};

use std::collections::{HashMap, HashSet};

/// See top-level crate documentation.
#[proc_macro_error]
#[proc_macro_derive(EnumFieldGetter)]
Expand All @@ -25,29 +25,45 @@ pub fn enum_field_getter(stream: TokenStream) -> TokenStream {
let ident = field.ident.clone().unwrap().to_string();
let field_ty = field.ty.clone();
let df = (field_ty.clone(), vec![variant.ident.to_string()]);
field_info.entry(ident.clone()).and_modify(|info| {
let (ty, used_variants) = info;
if quote!{#field_ty}.to_string() != quote!{#ty}.to_string() {
emit_warning!(field, "fields must be the same type across all variants - no getter will be emitted for this field");
incompatible.insert(ident.clone());
} else {
used_variants.push(variant.ident.to_string());
}
}).or_insert(df);
field_info
.entry(ident.clone())
.and_modify(|info| {
let (ty, used_variants) = info;
if quote! {#field_ty}.to_string() != quote! {#ty}.to_string() {
emit_warning!(
field,
"fields must be the same type across all variants - no getter \
will be emitted for this field"
);
incompatible.insert(ident.clone());
} else {
used_variants.push(variant.ident.to_string());
}
})
.or_insert(df);
}
} else if let Fields::Unnamed(_) = variant.fields {
for (i, field) in variant.fields.iter().enumerate() {
let field_ty = field.ty.clone();
let df = (field_ty.clone(), vec![variant.ident.to_string()]);
tuple_field_info.entry(i).and_modify(|info| {
let (ty, used_variants) = info;
if quote!{#field_ty}.to_string() != quote!{#ty}.to_string() {
emit_warning!(field, "Fields must be the same type across all variants - no getter will be emitted for this field.\nExpected type {}, got {}.", quote!{#ty}.to_string(), quote!{#field_ty}.to_string());
tuple_incompatible.insert(i);
} else {
used_variants.push(variant.ident.to_string());
}
}).or_insert(df);
tuple_field_info
.entry(i)
.and_modify(|info| {
let (ty, used_variants) = info;
if quote! {#field_ty}.to_string() != quote! {#ty}.to_string() {
emit_warning!(
field,
"Fields must be the same type across all variants - no getter \
will be emitted for this field.\nExpected type {}, got {}.",
quote! {#ty}.to_string(),
quote! {#field_ty}.to_string()
);
tuple_incompatible.insert(i);
} else {
used_variants.push(variant.ident.to_string());
}
})
.or_insert(df);
}
}
}
Expand Down
24 changes: 14 additions & 10 deletions playground/components/ProjectIdPlayer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
:title="title"
:instructions="instructions"
:description="description"
:zip="null"
:zip="zip"
></ProjectPlayer>
<template v-else>
<h1>Project not found</h1>
Expand All @@ -21,12 +21,14 @@
</template>

<script setup>
import { unpackProject } from "../lib/project-loader";
import ProjectPlayer from "./ProjectPlayer.vue";
import { ref } from "vue";
import { ref, nextTick } from "vue";

const props = defineProps(["id"]);
const success = ref(null);
const json = ref("");
const zip = ref("");
const title = ref("");
const author = ref("");
const instructions = ref("");
Expand All @@ -39,14 +41,16 @@ try {
author.value = apiRes.author.username;
instructions.value = apiRes.instructions;
description.value = apiRes.description;
json.value = await fetch(
`https://projects.scratch.mit.edu/${props.id}/?token=${apiRes.project_token}`,
).then((res) => {
if (!res.ok) {
throw new Error("response was not OK");
}
return res.json();
});
const res = await fetch(
`https://projects.scratch.mit.edu/${props.id}?token=${apiRes.project_token}`,
);
if (!res.ok) {
throw new Error("response was not OK");
}
const [_json, _zip] = await unpackProject(await res.arrayBuffer());
console.log(json);
zip.value = _zip;
json.value = _json;
success.value = true;
} catch (e) {
success.value = e;
Expand Down
2 changes: 2 additions & 0 deletions playground/components/ProjectPlayer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,8 @@ onMounted(async () => {
let assets = null;
let wasmProject;

console.log(props);

try {
// we need to convert settings to and from a JsValue because the WasmFlags exported from the
// no-compiler version is not the same as that exported by the compiler... because reasons
Expand Down
2 changes: 2 additions & 0 deletions playground/lib/project-loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export const unpackProject = (input) => {
if (typeof input !== "string") {
input = Buffer.from(input);
}
console.log(input);
return new Promise((resolve, reject) => {
// The second argument of false below indicates to the validator that the
// input should be parsed/validated as an entire project (and not a single sprite)
Expand Down Expand Up @@ -39,6 +40,7 @@ export const unpackProject = (input) => {
return Promise.reject(error);
})
.then(async ([json, zip]) => {
console.log(Object.keys(json), json.projectVersion);
if (json.projectVersion === 3) {
return [json, zip];
}
Expand Down
10 changes: 10 additions & 0 deletions playground/lib/project-runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ export async function instantiateProject({
requests_refresh,
threads,
unreachable_dbg,
sensing_timer,
} = instance.exports;
if (typeof window === "object") {
window.memory = memory;
Expand All @@ -180,11 +181,17 @@ export async function instantiateProject({
renderer.draw();

let startTime = Date.now();
let previousTickStartTime = startTime;
$outertickloop: while (running) {
if (timeout && Date.now() - startTime > timeout) {
return onTimeout();
}
let thisTickStartTime = Date.now();
if (typeof sensing_timer !== "undefined") {
sensing_timer.value +=
(thisTickStartTime - previousTickStartTime) / 1000;
}
previousTickStartTime = thisTickStartTime;
do {
tick();
if (threads_count.value === 0) {
Expand Down Expand Up @@ -213,6 +220,9 @@ export async function instantiateProject({
return {
greenFlag: () => {
console.log("green flag clicked");
if (typeof sensing_timer !== "undefined") {
sensing_timer.value = 0.0;
}
flag_clicked();
run();
},
Expand Down
4 changes: 4 additions & 0 deletions playground/views/HomeView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,10 @@ li.block-category-control {
color: #ffab19;
}

li.block-category-event {
color: #ffbf00;
}

li.block-category-pen {
color: #0fbd8c;
}
Expand Down
5 changes: 4 additions & 1 deletion rustfmt.toml
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
unstable_features = true
unstable_features = true
format_strings = true
imports_granularity = "Module"
group_imports = "StdExternalCrate"
2 changes: 1 addition & 1 deletion src/error.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use alloc::boxed::Box;
use core::cell::{BorrowError, BorrowMutError};

use alloc::boxed::Box;
use wasm_bindgen::JsValue;

pub type HQResult<T> = Result<T, HQError>;
Expand Down
64 changes: 56 additions & 8 deletions src/instructions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,21 @@

#![allow(
clippy::unnecessary_wraps,
reason = "many functions here needlessly return `Result`s in order to keep type signatures consistent"
reason = "many functions here needlessly return `Result`s in order to keep type signatures \
consistent"
)]
#![allow(
clippy::needless_pass_by_value,
reason = "there are so many `Rc<T>`s here which I don't want to change"
)]

use crate::ir::Step;
pub use crate::optimisation::{ConstFold, ConstFoldItem, ConstFoldState};
use crate::prelude::*;

mod control;
mod data;
mod event;
mod hq;
mod looks;
mod motion;
Expand Down Expand Up @@ -98,10 +101,55 @@ where

include!(concat!(env!("OUT_DIR"), "/ir-opcodes.rs"));

pub mod input_switcher;
pub use input_switcher::wrap_instruction;
impl IrOpcode {
pub fn yields_to_next_step(&self) -> Option<Rc<Step>> {
#[expect(
clippy::wildcard_enum_match_arm,
reason = "too many variants to match explicitly"
)]
match self {
Self::hq_yield(HqYieldFields {
mode: YieldMode::Schedule(next_step),
}) => Weak::upgrade(next_step),
Self::event_broadcast_and_wait(EventBroadcastAndWaitFields { next_step, .. })
| Self::procedures_call_nonwarp(ProceduresCallNonwarpFields { next_step, .. })
| Self::control_wait(ControlWaitFields { next_step, .. }) => Some(Rc::clone(next_step)),
_ => None,
}
}

pub fn inline_steps(&self) -> Option<Box<[Rc<Step>]>> {
#[expect(
clippy::wildcard_enum_match_arm,
reason = "too many variants to match explicitly"
)]
match self {
Self::hq_yield(HqYieldFields {
mode: YieldMode::Inline(inline_step),
}) => Some(Box::from([Rc::clone(inline_step)])),
Self::control_if_else(ControlIfElseFields {
branch_if,
branch_else,
}) => Some(Box::from([Rc::clone(branch_if), Rc::clone(branch_else)])),
Self::control_loop(ControlLoopFields {
first_condition,
condition,
body,
..
}) => Some(
[first_condition.as_ref(), Some(condition), Some(body)]
.into_iter()
.filter_map(Option::<&_>::cloned)
.collect(),
),
_ => None,
}
}
}

pub mod input_switcher;
pub use hq::r#yield::YieldMode;
pub use input_switcher::wrap_instruction;

/// Canonical NaN + bit 33, + string pointer in bits 1-32
pub const BOXED_STRING_PATTERN: i64 = 0x7FF8_0001 << 32;
Expand All @@ -114,11 +162,6 @@ pub const BOXED_COLOR_RGB_PATTERN: i64 = 0x7ff8_0008 << 32;
/// Canonical NaN + bit 37, + i32 in bits 1-32
pub const BOXED_COLOR_ARGB_PATTERN: i64 = 0x7ff8_000f << 32;
mod prelude {
pub use crate::ir::{ReturnType, Type as IrType};
pub use crate::optimisation::{ConstFold, ConstFoldItem, ConstFoldState};
pub use crate::prelude::*;
pub use crate::sb3::VarVal;
pub use crate::wasm::{InternalInstruction, StepFunc};
pub use ConstFold::NotFoldable;
pub use ReturnType::{MultiValue, Singleton};
pub use wasm_encoder::{RefType, ValType};
Expand All @@ -128,4 +171,9 @@ mod prelude {
BOXED_BOOL_PATTERN, BOXED_COLOR_ARGB_PATTERN, BOXED_COLOR_RGB_PATTERN, BOXED_INT_PATTERN,
BOXED_STRING_PATTERN,
};
pub use crate::ir::{ReturnType, Type as IrType};
pub use crate::optimisation::{ConstFold, ConstFoldItem, ConstFoldState};
pub use crate::prelude::*;
pub use crate::sb3::VarVal;
pub use crate::wasm::{InternalInstruction, StepFunc};
}
2 changes: 2 additions & 0 deletions src/instructions/control.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
pub mod get_thread_timeout;
pub mod if_else;
pub mod r#loop;
pub mod wait;
36 changes: 36 additions & 0 deletions src/instructions/control/get_thread_timeout.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
use wasm_encoder::{FieldType, HeapType, StorageType};

use super::super::prelude::*;

pub fn wasm(func: &StepFunc, _inputs: Rc<[IrType]>) -> HQResult<Vec<InternalInstruction>> {
let struct_type_index = func.registries().types().struct_(vec![FieldType {
element_type: StorageType::Val(ValType::F64),
mutable: false,
}])?;

Ok(wasm![
LocalGet(1),
RefCastNonNull(HeapType::Concrete(struct_type_index)),
StructGet {
struct_type_index,
field_index: 0
}
])
}

pub fn acceptable_inputs() -> HQResult<Rc<[IrType]>> {
Ok(Rc::from([]))
}

pub fn output_type(_inputs: Rc<[IrType]>) -> HQResult<ReturnType> {
Ok(Singleton(IrType::FloatPos))
}

pub const REQUESTS_SCREEN_REFRESH: bool = false;

pub const fn const_fold(
_inputs: &[ConstFoldItem],
_state: &mut ConstFoldState,
) -> HQResult<ConstFold> {
Ok(NotFoldable)
}
3 changes: 2 additions & 1 deletion src/instructions/control/if_else.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use wasm_encoder::BlockType;

use super::super::prelude::*;
use crate::ir::Step;
use wasm_encoder::BlockType;

#[derive(Debug)]
pub struct Fields {
Expand Down
3 changes: 2 additions & 1 deletion src/instructions/control/loop.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
// for use in warped contexts only.

use wasm_encoder::BlockType;

use super::super::prelude::*;
use crate::ir::Step;
use wasm_encoder::BlockType;

#[derive(Debug)]
pub struct Fields {
Expand Down
Loading
Loading