a decently improved architecture

This commit is contained in:
Aubrey 2025-03-23 20:33:38 -06:00
parent 5c87063c48
commit 6d238b24c1
No known key found for this signature in database
30 changed files with 2061 additions and 924 deletions

3
.envrc
View file

@ -1 +1,2 @@
use flake use flake
TMPDIR=/tmp

1115
Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

7
Cargo.toml Normal file
View file

@ -0,0 +1,7 @@
[workspace]
resolver = "3"
members = ["rust"]
[profile.release]
panic = "abort"
debug-assertions = true

View file

@ -2,114 +2,126 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins { plugins {
kotlin("jvm") version "2.1.10" kotlin("jvm") version "2.1.10"
id("fabric-loom") version "1.9-SNAPSHOT" id("fabric-loom") version "1.9-SNAPSHOT"
id("maven-publish") id("maven-publish")
id("com.google.protobuf") version "0.9.4"
} }
version = project.property("mod_version") as String version = project.property("mod_version") as String
group = project.property("maven_group") as String group = project.property("maven_group") as String
base { base {
archivesName.set(project.property("archives_base_name") as String) archivesName.set(project.property("archives_base_name") as String)
} }
val targetJavaVersion = 21 val targetJavaVersion = 21
java { java {
toolchain.languageVersion = JavaLanguageVersion.of(targetJavaVersion) toolchain.languageVersion = JavaLanguageVersion.of(targetJavaVersion)
// Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task // Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task
// if it is present. // if it is present.
// If you remove this line, sources will not be generated. // If you remove this line, sources will not be generated.
withSourcesJar() withSourcesJar()
} }
loom { loom {
mods { mods {
register("golemcomputers") { register("golemcomputers") {
sourceSet("main") sourceSet("main")
}
} }
}
} }
repositories { repositories {
// Add repositories to retrieve artifacts from in here. // Add repositories to retrieve artifacts from in here.
// You should only use this when depending on other mods because // You should only use this when depending on other mods because
// Loom adds the essential maven repositories to download Minecraft and libraries from automatically. // Loom adds the essential maven repositories to download Minecraft and libraries from automatically.
// See https://docs.gradle.org/current/userguide/declaring_repositories.html // See https://docs.gradle.org/current/userguide/declaring_repositories.html
// for more information about repositories. // for more information about repositories.
}
protobuf {
protoc {
artifact = "com.google.protobuf:protoc:4.30.0"
}
generateProtoTasks {
file("./proto/all.proto")
}
} }
configurations { configurations {
create("rustlib") { create("rustlib") {
isCanBeConsumed = false isCanBeConsumed = false
isCanBeResolved = true isCanBeResolved = true
} }
} }
dependencies { dependencies {
// To change the versions see the gradle.properties file // To change the versions see the gradle.properties file
minecraft("com.mojang:minecraft:${project.property("minecraft_version")}") minecraft("com.mojang:minecraft:${project.property("minecraft_version")}")
mappings("net.fabricmc:yarn:${project.property("yarn_mappings")}:v2") mappings("net.fabricmc:yarn:${project.property("yarn_mappings")}:v2")
modImplementation("net.fabricmc:fabric-loader:${project.property("loader_version")}") modImplementation("net.fabricmc:fabric-loader:${project.property("loader_version")}")
modImplementation("net.fabricmc:fabric-language-kotlin:${project.property("kotlin_loader_version")}") modImplementation("net.fabricmc:fabric-language-kotlin:${project.property("kotlin_loader_version")}")
modImplementation("net.fabricmc.fabric-api:fabric-api:${project.property("fabric_version")}") modImplementation("net.fabricmc.fabric-api:fabric-api:${project.property("fabric_version")}")
add("rustlib", project(":rust")) add("rustlib", project(":rust"))
implementation("com.github.jhg023:Pbbl:1.0.2") implementation("com.github.jhg023:Pbbl:1.0.2")
implementation("com.google.protobuf:protobuf-java:4.30.0")
protobuf(files("proto/"))
} }
tasks.processResources { tasks.processResources {
inputs.property("version", project.version) inputs.property("version", project.version)
inputs.property("minecraft_version", project.property("minecraft_version")) inputs.property("minecraft_version", project.property("minecraft_version"))
inputs.property("loader_version", project.property("loader_version")) inputs.property("loader_version", project.property("loader_version"))
filteringCharset = "UTF-8" filteringCharset = "UTF-8"
from(configurations.getByName("rustlib")) from(configurations.getByName("rustlib"))
filesMatching("fabric.mod.json") { filesMatching("fabric.mod.json") {
expand( expand(
"version" to project.version, "version" to project.version,
"minecraft_version" to project.property("minecraft_version"), "minecraft_version" to project.property("minecraft_version"),
"loader_version" to project.property("loader_version"), "loader_version" to project.property("loader_version"),
"kotlin_loader_version" to project.property("kotlin_loader_version") "kotlin_loader_version" to project.property("kotlin_loader_version")
) )
} }
} }
tasks.withType<JavaCompile>().configureEach { tasks.withType<JavaCompile>().configureEach {
// ensure that the encoding is set to UTF-8, no matter what the system default is // ensure that the encoding is set to UTF-8, no matter what the system default is
// this fixes some edge cases with special characters not displaying correctly // this fixes some edge cases with special characters not displaying correctly
// see http://yodaconditions.net/blog/fix-for-java-file-encoding-problems-with-gradle.html // see http://yodaconditions.net/blog/fix-for-java-file-encoding-problems-with-gradle.html
// If Javadoc is generated, this must be specified in that task too. // If Javadoc is generated, this must be specified in that task too.
options.encoding = "UTF-8" options.encoding = "UTF-8"
options.release.set(targetJavaVersion) options.release.set(targetJavaVersion)
} }
tasks.withType<KotlinCompile>().configureEach { tasks.withType<KotlinCompile>().configureEach {
compilerOptions.jvmTarget.set(JvmTarget.fromTarget(targetJavaVersion.toString())) compilerOptions.jvmTarget.set(JvmTarget.fromTarget(targetJavaVersion.toString()))
} }
tasks.jar { tasks.jar {
from("LICENSE") { from("LICENSE") {
rename { "${it}_${project.base.archivesName}" } rename { "${it}_${project.base.archivesName}" }
} }
} }
// configure the maven publication // configure the maven publication
publishing { publishing {
publications { publications {
create<MavenPublication>("mavenJava") { create<MavenPublication>("mavenJava") {
artifactId = project.property("archives_base_name") as String artifactId = project.property("archives_base_name") as String
from(components["java"]) from(components["java"])
}
} }
}
// See https://docs.gradle.org/current/userguide/publishing_maven.html for information on how to set up publishing. // See https://docs.gradle.org/current/userguide/publishing_maven.html for information on how to set up publishing.
repositories { repositories {
// Add repositories to publish to here. // Add repositories to publish to here.
// Notice: This block does NOT have the same function as the block in the top level. // Notice: This block does NOT have the same function as the block in the top level.
// The repositories here will be used for publishing your artifact, not for // The repositories here will be used for publishing your artifact, not for
// retrieving dependencies. // retrieving dependencies.
} }
} }

View file

@ -32,6 +32,7 @@
cmake cmake
ninja ninja
pkg-config pkg-config
protobuf
#needed for minecraft to function properly #needed for minecraft to function properly
libGL libGL

28
proto/all.proto Normal file
View file

@ -0,0 +1,28 @@
syntax = "proto3";
package computer;
option java_package = "ca.sanae.golemcomputers.computer";
option java_outer_classname = "NativeProtobuf";
message ComputerInit {
repeated ComponentInit components = 1;
uint32 primary_ram_pages = 2;
sint32 primary_rom_index = 3;
}
message ComponentInit {
oneof data {
CoreComponentInit core = 1;
ChatComponentInit chat = 2;
}
}
message CoreComponentInit {
uint32 instructions_per_tick = 1;
}
message ChatComponentInit {
sint32 java_index = 1;
}

View file

@ -10,15 +10,16 @@ crate-type = ["cdylib", "rlib"]
bitfield-struct = "0.10.1" bitfield-struct = "0.10.1"
disarm64 = "0.1.24" disarm64 = "0.1.24"
jni = "0.21.1" jni = "0.21.1"
nodit = "0.9.2"
num-derive = "0.4.2" num-derive = "0.4.2"
num-traits = "0.2.19" num-traits = "0.2.19"
oneshot = "0.1.10" oneshot = "0.1.10"
pollster = "0.4.0" pollster = "0.4.0"
prost = "0.13.5"
smol = "2.0.2" smol = "2.0.2"
static_assertions = "1.1.0" static_assertions = "1.1.0"
unicorn-engine = { version = "2.1.1", default-features = false, features = ["arch_aarch64"], git = "https://github.com/Sanae6/unicorn/", branch = "cpreg" } unicorn-engine = { version = "2.1.1", default-features = false, features = ["arch_aarch64"], git = "https://github.com/Sanae6/unicorn/", branch = "cpreg" }
zerocopy = { version = "0.8.20", features = ["derive"] } zerocopy = { version = "0.8.20", features = ["derive"] }
[profile.release] [build-dependencies]
panic = "abort" prost-build = "0.13.5"
debug-assertions = true

View file

@ -1,20 +1,18 @@
val task = tasks.register<Exec>("default") { val task = tasks.register<Exec>("default") {
commandLine("cargo", "build", "--release") commandLine("cargo", "build", "--release")
val library = System.mapLibraryName("golem_computers") val library = System.mapLibraryName("golem_computers")
val libraryPath = "$workingDir/target/release/$library" val libraryPath = "$workingDir/../target/release/$library"
outputs.file(libraryPath) outputs.file(libraryPath)
inputs.dir("$workingDir/src") inputs.dir("$workingDir/src")
doLast { doLast {
if (!file(libraryPath).exists()) { if (!file(libraryPath).exists()) {
throw GradleException("the library wasn't properly build! expected $libraryPath") throw GradleException("the library wasn't properly built! expected $libraryPath")
} }
} }
} }
configurations { configurations {
create("default") { create("default")
}
} }
artifacts.add("default", task) artifacts.add("default", task)

6
rust/build.rs Normal file
View file

@ -0,0 +1,6 @@
use std::io::Result;
fn main() -> Result<()> {
prost_build::compile_protos(&["../proto/all.proto"], &["../proto"])?;
Ok(())
}

37
rust/examples/condvar.rs Normal file
View file

@ -0,0 +1,37 @@
use std::{
sync::{Arc, Condvar, Mutex},
thread,
time::Duration,
};
fn main() {
let pair = Arc::new((Mutex::new(false), Condvar::new()));
let pair_clone1 = Arc::clone(&pair);
let pair_clone2 = Arc::clone(&pair);
thread::spawn(move || {
let (lock, cvar) = &*pair_clone1;
let mut started = lock.lock().unwrap();
*started = true;
std::thread::sleep(Duration::from_secs(3));
cvar.notify_all();
});
thread::spawn(move || {
let (lock, cvar) = &*pair_clone2;
let mut started = lock.lock().unwrap();
while !*started {
println!("waiting 2");
started = cvar.wait(started).unwrap();
}
println!("Thread 2 proceeding...");
});
let (lock, cvar) = &*pair;
let mut started = lock.lock().unwrap();
while !*started {
println!("waiting 1");
started = cvar.wait(started).unwrap();
}
println!("Thread 1 proceeding...");
}

View file

@ -1,251 +0,0 @@
use std::{
mem::offset_of,
ops::{Deref, DerefMut, Range},
pin::Pin,
sync::{atomic::Ordering, Arc},
};
use bitfield_struct::bitfield;
use jni::JavaVM;
use unicorn_engine::Unicorn;
use zerocopy::{
big_endian::U16 as U16BE,
little_endian::{U16, U32, U64},
FromBytes, Immutable, IntoBytes, KnownLayout,
};
use crate::{
align_up, component::{Component, ComponentInterface}, core::CpuContext, memory_map::RAM_START, overlapping, range_of_field, unsync_cell::UnsyncCell, unsync_read
};
use super::ComponentDma;
pub struct ComponentIoResult {
component_index: usize,
direction: ComponentIoDirection,
}
pub enum ComponentIoDirection {
ToComponent,
ToGuest,
}
impl Component {
pub fn perform_write<D>(
&self,
range: Range<usize>,
writer: impl FnOnce(&UnsyncCell<ComponentInterface>),
ram: Arc<UnsyncCell<[u8]>>,
java_vm: JavaVM,
) -> Option<Pin<Box<dyn Future<Output = ComponentIoResult>>>> {
let Self::Populated {
read_transfer_active,
write_transfer_active,
info,
interface,
own_index,
java_component,
..
} = self
else {
return None;
};
if overlapping(&range, &range_of_field!(ComponentInterface, info)) {
return None;
}
let read_range = range_of_field!(ComponentInterface, read_dma);
if overlapping(&range, &read_range) {
if read_transfer_active.load(Ordering::Acquire) || read_range != range {
return None;
}
writer(interface);
let dma: ComponentDma = unsync_read!(interface, ComponentInterface, read_dma);
if dma.dma_enabled()
&& read_transfer_active.compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire).is_ok()
{
if !overlapping(
&(dma.address..dma.address + dma.size()),
&(RAM_START..RAM_START + ram.len() as u64),
) {
read_transfer_active.store(false, Ordering::Release);
return None;
}
let bytes_per_chunk: u32 = info.bytes_per_tick / 5;
let java_component =
let index = return Some(Box::pin(async move {
let java_component = java_component;
ComponentIoResult { component_index: 0, direction: todo!() }
}));
}
return None;
}
// let write_range = range_of_field!(ComponentInterface, write_dma);
// if write_transfer_active && overlapping(range.clone(), write_range) {
// return;
// }
return None;
}
}
#[derive(FromBytes, IntoBytes, Immutable, KnownLayout)]
#[repr(C)]
struct ComponentMmioHeader {
component_count: U32,
/// version bump when component access method (offset table) changes or [ComponentInfo] is non-additively changed
version: U16,
control: U16BE,
}
#[bitfield(u16)]
#[derive(FromBytes, IntoBytes, Immutable, KnownLayout)]
pub struct ComponentMmioControl {
interrupt_added: bool,
interrupt_removed: bool,
#[bits(2)]
_reserved: U16,
/// read-write, indicates the core to fire interrupts on. indicating an invalid core means no core will receive the interrupt
#[bits(4)]
interrupt_firing_core: u8,
#[bits(8)]
_reserved: U16,
}
pub fn calculate_components_start(component_count: usize) -> usize {
OFFSETS_START + align_up(component_count as u64 * 4 + 16, 0x1000) as usize
}
fn read_bytes_to_u64(bytes: &[u8]) -> u64 {
match bytes.len() {
1 => bytes[0] as u64,
2 => U16::read_from_bytes(bytes).unwrap().get() as u64,
4 => U32::read_from_bytes(bytes).unwrap().get() as u64,
8 => U64::read_from_bytes(bytes).unwrap().get() as u64,
16 => todo!("128 bit reads are currently neither implemented here nor supported by unicorn hooks"),
_ => panic!("expected byte slice of primitive size"),
}
}
fn write_bytes_from_u64(bytes: &mut [u8], value: u64) {
match bytes.len() {
1 => bytes[0] = value.try_into().unwrap(),
2 => U16::new(value.try_into().unwrap()).write_to(bytes).unwrap(),
4 => U32::new(value.try_into().unwrap()).write_to(bytes).unwrap(),
8 => U64::new(value).write_to(bytes).unwrap(),
16 => todo!("128 bit writes are currently neither implemented here nor supported by unicorn hooks"),
_ => panic!("expected byte slice of primitive size"),
}
}
const OFFSETS_START: usize = 0x200;
const INTERFACE_INTERVAL: usize = 0x800;
pub fn component_mmio_read(vcpu: &mut Unicorn<CpuContext>, offset: u64, size: usize) -> u64 {
let offset = offset as usize;
let component_count = vcpu.get_data().components.len();
if offset < OFFSETS_START {
// mmio header
println!("reading from mmio start!");
let header = ComponentMmioHeader {
component_count: U32::new(component_count as u32),
version: U16::new(0),
control: U16BE::new(vcpu.get_data().component_control.read().into_bits()),
};
if offset > size_of::<ComponentMmioHeader>() {
return 0;
}
let mut region = [0; size_of::<ComponentMmioHeader>() + size_of::<u64>()];
header.write_to_prefix(&mut region).unwrap();
let range = offset as usize..(offset + size) as usize;
return read_bytes_to_u64(&region[range]);
}
if offset < vcpu.get_data().component_data_start {
// offsets
let range = offset as usize..(offset + size) as usize;
let components_range = OFFSETS_START..(size_of::<u32>() * component_count);
let range = range.start - components_range.start as usize..range.end - components_range.start as usize;
let first_offset_index = range.start >> 2;
let offset_buffer: [U32; 3] = std::array::from_fn(|index| {
(first_offset_index + index < component_count as usize)
.then_some(U32::new(
((first_offset_index + index) * INTERFACE_INTERVAL as usize) as u32,
))
.unwrap_or_default()
});
return read_bytes_to_u64(&offset_buffer.as_bytes()[range]);
}
// info region
let offset = offset - vcpu.get_data().component_data_start;
let index = offset / INTERFACE_INTERVAL as usize;
let offset = offset - (index * INTERFACE_INTERVAL as usize);
let Some(Component::Populated { interface, .. }) = vcpu.get_data().components.get(index) else {
return 0;
};
match size {
1 => interface.read_into::<u8>(offset).into(),
2 => interface.read_into::<U16>(offset).into(),
4 => interface.read_into::<U32>(offset).into(),
8 => interface.read_into::<U64>(offset).into(),
_ => unreachable!(),
}
}
pub fn component_mmio_write(vcpu: &mut Unicorn<CpuContext>, offset: u64, size: usize, value: u64) {
let offset = offset as usize;
let range = offset..offset + size;
if offset < OFFSETS_START {
if overlapping(
&range,
&(offset_of!(ComponentMmioHeader, control)..size_of::<ComponentMmioHeader>()),
) {
// control included
let mut buffer = [0; size_of::<ComponentMmioHeader>() + size_of::<u64>()];
write_bytes_from_u64(&mut buffer[range], value);
let header = ComponentMmioHeader::ref_from_prefix(&buffer).unwrap().0;
vcpu.get_data().component_control.write(ComponentMmioControl::from_bits(header.control.get()));
}
return;
}
if offset < vcpu.get_data().component_data_start {
return;
}
let offset = offset - vcpu.get_data().component_data_start;
let index = offset / INTERFACE_INTERVAL as usize;
let offset = offset - (index * INTERFACE_INTERVAL as usize);
let Some(component) = vcpu.get_data().components.get(index) else {
return;
};
if offset > size_of::<ComponentInterface>() {
return;
}
component.perform_write(
range,
|interface| match size {
1 => interface.write_from(offset, &(value as u8)),
2 => interface.write_from(offset, &U16::new(value as u16)),
4 => interface.write_from(offset, &U32::new(value as u32)),
8 => interface.write_from(offset, &U64::new(value as u64)),
_ => unreachable!(),
},
vcpu.get_data().ram,
);
}

View file

@ -1,120 +0,0 @@
pub mod mmio;
use std::{
cell::RefCell,
mem::offset_of,
ops::{Deref, DerefMut, Range, RangeBounds},
pin::Pin,
rc::Rc,
sync::{
atomic::{AtomicBool, Ordering},
mpsc, Arc, LazyLock, Mutex,
},
};
use bitfield_struct::bitfield;
use jni::objects::GlobalRef;
use unicorn_engine::Unicorn;
use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
use crate::{
core::CpuContext, memory_map::RAM_START, overlapping, range_of_field, subtract_range, unsync_cell::UnsyncCell,
unsync_read,
};
#[derive(Default)]
pub enum Component {
#[default]
Empty,
Populated {
own_index: usize,
java_component: GlobalRef,
read_transfer_active: AtomicBool,
write_transfer_active: AtomicBool,
info: ComponentInfo,
interface: UnsyncCell<ComponentInterface>,
},
}
#[derive(Clone, Copy, Default, FromBytes, IntoBytes, Immutable, KnownLayout)]
#[repr(C)]
pub struct ComponentInterface {
// read only
pub info: ComponentInfo,
// read-write
control: ComponentControl,
_restart_device: u8,
_reserved: [u8; 6],
read_dma: ComponentDma,
write_dma: ComponentDma,
}
#[bitfield(u8)]
#[derive(FromBytes, IntoBytes, Immutable, KnownLayout)]
pub struct ComponentControl {
interrupt_when_added: bool,
interrupt_when_removed: bool,
#[bits(2)]
_reserved: u8,
#[bits(4)]
interrupt_firing_core: u8,
}
#[derive(Clone, Copy, Default, FromBytes, IntoBytes, Immutable, KnownLayout)]
#[repr(C)]
pub struct ComponentDma {
pub upper_word: ComponentDmaUpper,
pub address: u64,
}
impl Deref for ComponentDma {
type Target = ComponentDmaUpper;
fn deref(&self) -> &Self::Target {
&self.upper_word
}
}
impl DerefMut for ComponentDma {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.upper_word
}
}
#[bitfield(u64)]
#[derive(FromBytes, IntoBytes, Immutable, KnownLayout)]
pub struct ComponentDmaUpper {
dma_enabled: bool,
#[bits(3)]
_reserved: u8,
/// read-write, indicates the core to fire interrupts on. indicating an invalid core means no core will receive the interrupt
#[bits(4)]
interrupt_firing_core: u8,
#[bits(56)]
size: u64,
}
#[derive(Clone, Copy, FromBytes, IntoBytes, Immutable, KnownLayout)]
#[repr(C)]
pub struct ComponentInfo {
/// a null terminated string, the type name of the component
pub type_name: [u8; 256],
/// a null terminated string, the protocol identifier for how to talk to the component
pub protocol: [u8; 256],
pub bytes_per_tick: u32,
pub version: u32,
}
impl Default for ComponentInfo {
fn default() -> Self {
Self {
type_name: [0; 256],
protocol: [0; 256],
bytes_per_tick: 0,
version: 0,
}
}
}

View file

@ -0,0 +1,72 @@
use std::{mem::offset_of, sync::{mpsc, Arc}};
use bitfield_struct::bitfield;
use zerocopy::{
little_endian::{U32, U64},
FromBytes, IntoBytes, KnownLayout,
};
use crate::{component_name, core::CoreMessage, overlapping, unsync_cell::UnsyncCell};
use super::Component;
#[derive(Clone, Copy, FromBytes, IntoBytes, KnownLayout)]
#[repr(C)]
pub struct Registers {
pub reset_address: U64,
pub flags: U32,
}
#[bitfield(u32)]
pub struct Flags {
should_reset: bool,
#[bits(31)]
_reserved: u32,
}
pub struct CoreComponent {
registers: Arc<UnsyncCell<Registers>>,
message_sender: mpsc::Sender<CoreMessage>,
}
impl CoreComponent {
pub fn new(registers: Arc<UnsyncCell<Registers>>, message_sender: mpsc::Sender<CoreMessage>) -> Self {
Self {
registers,
message_sender,
}
}
}
impl Component for CoreComponent {
fn name() -> [u8; 0x30] {
component_name!(b"core")
}
fn memory_map_size(&self) -> u64 {
size_of::<Registers>() as u64
}
fn read(&self, _: &mut jni::JNIEnv, offset: u64, data: &mut [u8]) {
assert!((offset + data.len() as u64) < self.memory_map_size() as u64);
self.registers.read_into_byte_slice(offset as usize, data);
}
fn write(&self, _: &mut jni::JNIEnv, offset: u64, data: &[u8]) {
assert!((offset + data.len() as u64) < self.memory_map_size() as u64);
self.registers.write_from_byte_slice(offset as usize, data);
if overlapping(
&(offset..offset + data.len() as u64),
&(offset_of!(Registers, flags) as u64..size_of::<Registers>() as u64),
) {
let mut registers = self.registers.read();
let flags = Flags::from_bits(registers.flags.get());
if flags.should_reset() {
let _ = self.message_sender.send(CoreMessage::Reset);
registers.flags.set(flags.with_should_reset(false).into_bits());
self.registers.write(registers);
}
}
}
}

View file

@ -0,0 +1,50 @@
use std::sync::Mutex;
use jni::{
objects::{GlobalRef, JValueGen},
JNIEnv,
};
use crate::component_name;
use super::Component;
pub struct DebugChat {
buffer: Mutex<String>,
object: GlobalRef,
}
impl DebugChat {
pub(crate) fn new(component: GlobalRef) -> Self {
Self {
buffer: Default::default(),
object: component,
}
}
}
impl Component for DebugChat {
fn name() -> [u8; 0x30] {
component_name!(b"chat")
}
fn memory_map_size(&self) -> u64 {
1
}
fn write(&self, env: &mut JNIEnv, _: u64, data: &[u8]) {
let mut string = self.buffer.lock().unwrap();
if data[0] == 0 {
let jni_string = env.new_string(string.drain(..).collect::<String>()).expect("failed to create string");
env
.call_method(
self.object.clone(),
"sendChatMessage",
"(Ljava_lang_String;)V",
&[JValueGen::Object(&jni_string)],
)
.expect("failed to call...");
return;
}
string.push(data[0].into());
}
}

View file

@ -0,0 +1,29 @@
use bitfield_struct::bitfield;
use crate::component_name;
use super::Component;
#[bitfield(u8)]
struct InterruptEntry {
#[bits(4)]
affinity: u8,
masked: bool,
fired: bool,
#[bits(2)]
_unused: u8,
}
pub struct InterruptController {
_interrupt_entries: [InterruptEntry; 32],
}
impl Component for InterruptController {
fn name() -> [u8; 0x30] {
component_name!(b"interrupt_controller")
}
fn memory_map_size(&self) -> u64 {
0
}
}

View file

@ -0,0 +1,66 @@
use core::CoreComponent;
use debug_chat::DebugChat;
use jni::JNIEnv;
pub mod core;
pub mod debug_chat;
pub mod interrupt_controller;
pub mod storage;
pub trait Component: Sized {
fn name() -> [u8; 0x30];
fn memory_map_size(&self) -> u64;
fn write(&self, env: &mut JNIEnv, offset: u64, data: &[u8]) {
let _ = (env, offset, data);
}
fn read(&self, env: &mut JNIEnv, offset: u64, data: &mut [u8]) {
let _ = (env, offset, data);
}
fn has_interrupt(&self) -> bool {
false
}
}
pub enum AllComponent {
Core(CoreComponent),
DebugChat(DebugChat),
}
impl AllComponent {
pub fn name(&self) -> [u8; 0x30] {
match self {
AllComponent::Core(_) => CoreComponent::name(),
AllComponent::DebugChat(_) => DebugChat::name(),
}
}
pub fn memory_map_size(&self) -> u64 {
match self {
AllComponent::Core(core_component) => core_component.memory_map_size(),
AllComponent::DebugChat(debug_chat) => debug_chat.memory_map_size(),
}
}
pub fn write(&self, env: &mut JNIEnv, offset: u64, data: &[u8]) {
match self {
AllComponent::Core(core_component) => core_component.write(env, offset, data),
AllComponent::DebugChat(debug_chat) => debug_chat.write(env, offset, data),
}
}
pub fn read(&self, env: &mut JNIEnv, offset: u64, data: &mut [u8]) {
match self {
AllComponent::Core(core_component) => core_component.read(env, offset, data),
AllComponent::DebugChat(debug_chat) => debug_chat.read(env, offset, data),
}
}
pub fn has_interrupt(&self) -> bool {
match self {
AllComponent::Core(core_component) => core_component.has_interrupt(),
AllComponent::DebugChat(debug_chat) => debug_chat.has_interrupt(),
}
}
}

View file

@ -0,0 +1,2 @@
pub struct Storage {
}

View file

@ -1,86 +1,114 @@
use std::sync::{Arc, OnceLock, RwLock}; pub(crate) mod proto {
include!(concat!(env!("OUT_DIR"), "/computer.rs"));
}
use std::sync::{mpsc, Arc};
use jni::{ use jni::{
objects::{JByteBuffer, JIntArray, JObject, JString, JValue}, objects::{JByteArray, JByteBuffer, JObject, JObjectArray, JValue},
signature::{Primitive, ReturnType}, signature::{Primitive, ReturnType},
strings::JNIString, sys::jlong,
sys::{jint, jlong},
JNIEnv, JNIEnv,
}; };
use prost::Message;
use proto::component_init::Data;
use zerocopy::{little_endian::U64, FromZeros};
use crate::{ use crate::{
assert_positive, align_up_to_page,
component::{mmio::ComponentMmioControl, Component, ComponentInfo, ComponentInterface}, components::{
core::CoreHandle, core::{CoreComponent, Registers},
debug_chat::DebugChat,
AllComponent,
},
core::{spawn_core_thread, Core, CoreMessage},
device_bus::{DeviceBus, ROM_START},
unsync_cell::UnsyncCell, unsync_cell::UnsyncCell,
}; };
pub struct Computer { pub struct Computer {
pub components: Arc<[RwLock<Component>]>, cores: Vec<mpsc::Sender<CoreMessage>>,
pub component_control: Arc<UnsyncCell<ComponentMmioControl>>, }
pub shared_ram: Arc<UnsyncCell<[u8]>>,
pub shared_rom: Arc<UnsyncCell<[u8]>>,
pub cores: OnceLock<Box<[CoreHandle]>>, fn try_get_object<'local>(
env: &mut JNIEnv<'local>,
init_objects: &JObjectArray<'local>,
index: i32,
) -> jni::errors::Result<Option<JObject<'local>>> {
if index < 0 {
return Ok(None);
}
env.get_object_array_element(&init_objects, index).map(Some)
} }
impl Computer { impl Computer {
#[unsafe(export_name = "Java_ca_sanae_golemcomputers_computer_Computer_new")] #[unsafe(export_name = "Java_ca_sanae_golemcomputers_computer_Computer_new")]
extern "C" fn new<'local>( unsafe extern "C" fn new<'local>(
mut env: JNIEnv<'local>, mut env: JNIEnv<'local>,
java_computer: JObject<'local>, java_computer: JObject<'local>,
component_capacity: i32, init_data: JByteArray<'local>,
memory_size: i32, init_objects: JObjectArray<'local>,
core_speeds: JIntArray<'local>,
) { ) {
let (component_capacity, memory_size): (usize, usize) = ( let init_data = env.convert_byte_array(&init_data).expect("unable to convert byte array to vec");
assert_positive!(env, component_capacity), let init = proto::ComputerInit::decode(init_data.as_slice()).expect("failed to decode init data from java!");
assert_positive!(env, memory_size), let ram = UnsyncCell::new_zeroed_box(init.primary_ram_pages as usize * 0x1000);
);
let core_count = env.get_array_length(&core_speeds).unwrap() as usize; let rom = try_get_object(&mut env, &init_objects, init.primary_rom_index)
if core_count > 16 { .expect("failed to get rom from java")
env.throw("no more than 16 cores are allowed").unwrap(); .map(|rom| {
return; if !env.is_instance_of(&rom, "java/nio/ByteBuffer").expect("failed to perform instanceOf") {
} panic!("rom was not a byte buffer")
println!("got to computer construction!"); }
let computer = Box::new(Self { let rom_byte_buf = JByteBuffer::from(rom);
components: { let capacity = env.get_direct_buffer_capacity(&rom_byte_buf).expect("unable to extract capacity from rom");
let mut components = Vec::with_capacity(component_capacity); let rom = UnsyncCell::new_zeroed_box(align_up_to_page(capacity));
components.extend(std::iter::from_fn(|| Default::default()).take(component_capacity)); rom.update_from_jni(&mut env, &rom_byte_buf).unwrap();
Arc::from(components.into_boxed_slice()) rom
},
component_control: Arc::new(UnsyncCell::new(ComponentMmioControl::new())),
shared_ram: Arc::from(UnsyncCell::new_zeroed_box(memory_size as usize)),
shared_rom: Arc::from(UnsyncCell::new_zeroed_box(0x1000000 as usize)),
// late initalized, see below
cores: OnceLock::new(),
});
println!("core construction");
let mut speeds = [0; 16];
env.get_int_array_region(core_speeds, 0, &mut speeds[..core_count]).unwrap();
println!("core speeds acquired {:?}", &speeds[..core_count]);
let Ok(cores) = speeds
.into_iter()
.take(core_count)
.enumerate()
.map(|(core_index, speed)| {
Ok(CoreHandle::new(
&computer,
core_index,
assert_positive!(env, speed, Err(())),
env.get_java_vm().unwrap(),
))
}) })
.collect::<Result<Vec<_>, _>>() .expect("no rom was provided");
else {
return;
};
let _ = computer.cores.set(cores.into_boxed_slice()); let mut core_init_datas = Vec::new();
let components = init
.components
.into_iter()
.map(|component| match component.data.unwrap() {
Data::Core(init) => {
let registers = Arc::new(UnsyncCell::new(Registers {
reset_address: U64::new(ROM_START),
..Registers::new_zeroed()
}));
let (sender, receiver) = mpsc::channel();
core_init_datas.push((init, registers.clone(), sender.clone(), receiver));
AllComponent::Core(CoreComponent::new(registers, sender))
}
Data::Chat(init) => {
let component = try_get_object(&mut env, &init_objects, init.java_index)
.expect("failed to get chat component from java")
.expect("chat component object was not provided");
println!("storing computer info in field!"); let component = env.new_global_ref(component).unwrap();
AllComponent::DebugChat(DebugChat::new(component))
}
})
.collect();
let device_bus = Arc::new(DeviceBus::new(ram, rom, components));
let cores = core_init_datas
.into_iter()
.map(|(init, registers, sender, receiver)| {
spawn_core_thread(
Core::new(registers, device_bus.clone(), init.instructions_per_tick),
env.get_java_vm().unwrap(),
receiver,
);
sender
})
.collect();
let computer = Box::new(Self { cores });
env env
.set_field( .set_field(
@ -104,67 +132,6 @@ impl Computer {
.unwrap() as *mut _ .unwrap() as *mut _
} }
#[unsafe(export_name = "Java_ca_sanae_golemcomputers_computer_Computer_insertComponentNative")]
unsafe extern "C" fn insert_component<'local>(
mut env: JNIEnv<'local>,
computer: JObject<'local>,
component_index: jint,
component_object: JObject<'local>,
type_name: JString<'local>,
protocol: JString<'local>,
bytes_per_tick: u32,
version: u32,
) {
// Safety: the responsibility of asserting the pointer is valid is on the java caller
let computer = unsafe { &*Self::from_jni(&mut env, computer) };
let component_index: usize = assert_positive!(env, component_index);
let mut make_info_string = |string: JString<'local>| {
let mut bytes = [0u8; 256];
let jstring = env.get_string(&string).unwrap();
let length = bytes.len().min(jstring.to_bytes().len());
bytes[..length].copy_from_slice(&jstring.to_bytes()[..length]);
bytes
};
let type_name = make_info_string(type_name);
let protocol = make_info_string(protocol);
let info = ComponentInfo {
type_name,
protocol,
bytes_per_tick,
version,
};
let java_component = env.new_global_ref(component_object).unwrap();
*computer.components[component_index].write().unwrap() = Component::Populated {
own_index: component_index,
java_component,
read_transfer_active: false.into(),
write_transfer_active: false.into(),
info,
interface: ComponentInterface::default().into(),
};
}
#[unsafe(export_name = "Java_ca_sanae_golemcomputers_computer_Computer_updateRomTest")]
unsafe extern "C" fn update_rom_test<'local>(
mut env: JNIEnv<'local>,
computer: JObject<'local>,
bytes: JByteBuffer<'local>,
) {
// Safety: the responsibility of asserting the pointer is valid is on the java caller
let computer = unsafe { &*Self::from_jni(&mut env, computer) };
computer.shared_rom.update_from_jni(&mut env, &bytes).expect("failed to update rom");
}
#[unsafe(export_name = "Java_ca_sanae_golemcomputers_computer_Computer_updateRomFile")]
unsafe extern "C" fn update_rom_file<'local>(env: JNIEnv<'local>, computer: JObject<'local>) {
drop((env, computer));
todo!()
}
#[unsafe(export_name = "Java_ca_sanae_golemcomputers_computer_Computer_free")] #[unsafe(export_name = "Java_ca_sanae_golemcomputers_computer_Computer_free")]
unsafe extern "C" fn free<'local>(mut env: JNIEnv<'local>, computer: JObject<'local>) { unsafe extern "C" fn free<'local>(mut env: JNIEnv<'local>, computer: JObject<'local>) {
let computer = Self::from_jni(&mut env, computer); let computer = Self::from_jni(&mut env, computer);
@ -172,31 +139,23 @@ impl Computer {
drop(unsafe { Box::from_raw(computer) }) drop(unsafe { Box::from_raw(computer) })
} }
#[unsafe(export_name = "Java_ca_sanae_golemcomputers_computer_Computer_wakeCoreAt")] #[unsafe(export_name = "Java_ca_sanae_golemcomputers_computer_Computer_resetCore")]
unsafe extern "C" fn wake_core_at<'local>( unsafe extern "C" fn reset_core<'local>(mut env: JNIEnv<'local>, computer: JObject<'local>, core: u32, wake: bool) {
mut env: JNIEnv<'local>,
computer: JObject<'local>,
core: jint,
address: jlong,
) {
let (core, address): (usize, u64) = (assert_positive!(env, core), assert_positive!(env, address));
// Safety: the responsibility of asserting the pointer is valid is on the java caller // Safety: the responsibility of asserting the pointer is valid is on the java caller
let computer = unsafe { &*Self::from_jni(&mut env, computer) }; let computer = unsafe { &*Self::from_jni(&mut env, computer) };
let core = computer.cores.get(core as usize).unwrap();
let Some(core) = computer.cores.get().unwrap().get(core) else { core.send(CoreMessage::Reset).unwrap();
env.throw("core index was out of bounds").unwrap(); if wake {
return; core.send(CoreMessage::Wake).unwrap();
}; }
core.wake_at(address);
} }
#[unsafe(export_name = "Java_ca_sanae_golemcomputers_computer_Computer_tick")] #[unsafe(export_name = "Java_ca_sanae_golemcomputers_computer_Computer_tick")]
unsafe extern "C" fn tick<'local>(mut env: JNIEnv<'local>, computer: JObject<'local>) { unsafe extern "C" fn tick<'local>(mut env: JNIEnv<'local>, computer: JObject<'local>) {
// Safety: the responsibility of asserting the pointer is valid is on the java caller // Safety: the responsibility of asserting the pointer is valid is on the java caller
let computer = unsafe { &*Self::from_jni(&mut env, computer) }; let computer = unsafe { &*Self::from_jni(&mut env, computer) };
let cores = &computer.cores.get().unwrap(); for core in &computer.cores {
for core in cores.iter() { core.send(CoreMessage::Tick).unwrap();
core.tick();
} }
} }
} }

View file

@ -1,103 +1,122 @@
use std::{ use std::sync::{mpsc, Arc};
cell::{OnceCell, RefCell},
pin::Pin,
rc::Rc,
sync::{mpsc, Arc, RwLock},
};
use disarm64::decoder; use disarm64::decoder;
use jni::{JNIEnv, JavaVM}; use jni::{AttachGuard, JNIEnv, JavaVM};
use num_derive::FromPrimitive; use num_derive::FromPrimitive;
use num_traits::FromPrimitive; use num_traits::FromPrimitive;
use unicorn_engine::{Arch, ArmCpRegister, HookType, Mode, Permission, Query, RegisterARM64, Unicorn}; use unicorn_engine::{Arch, HookType, Mode, Unicorn};
use zerocopy::IntoBytes; use zerocopy::IntoBytes;
use crate::{component::{mmio::{calculate_components_start, component_mmio_read, component_mmio_write, ComponentMmioControl}, Component, ComponentIoResult}, computer::Computer, unsync_cell::UnsyncCell}; use crate::{components::core::Registers, device_bus::DeviceBus, unsync_cell::UnsyncCell};
pub struct CoreHandle { pub struct Core {
message_sender: mpsc::Sender<CpuMessage>, registers: Arc<UnsyncCell<Registers>>,
bus: Arc<DeviceBus>,
instructions_per_tick: u32,
} }
/* impl Core {
pub fn new(registers: Arc<UnsyncCell<Registers>>, bus: Arc<DeviceBus>, instructions_per_tick: u32) -> Self {
Self {
registers,
todo REWRITEEEEEEE RAAAAH bus,
instructions_per_tick,
}
}
*/
pub struct CpuContext<'a> {
component_data_start: usize,
components: Arc<[RwLock<Component>]>,
/// see comment on [Computer]
component_control: Arc<UnsyncCell<ComponentMmioControl>>,
message_sender: mpsc::Sender<CpuMessage>,
component_access_futures: Vec<Pin<Box<dyn Future<Output = ComponentIoResult>>>>,
ram: Arc<UnsyncCell<[u8]>>,
stopped: bool,
_env: JNIEnv<'a>,
} }
enum CpuMessage { pub struct CoreContext<'a> {
core: Core,
env: AttachGuard<'a>,
is_sleeping: bool,
}
impl<'a> CoreContext<'a> {
pub fn get_env(&mut self) -> &mut JNIEnv<'a> {
&mut self.env
}
}
pub enum CoreMessage {
Tick, Tick,
Wake(Option<u64>), Reset,
Wake,
Sleep, Sleep,
} }
impl CoreHandle { pub fn spawn_core_thread(core: Core, jvm: JavaVM, message_receiver: mpsc::Receiver<CoreMessage>) {
pub fn new(computer: &Computer, core_index: usize, instructions_per_tick: u32, java_vm: JavaVM) -> Self { std::thread::spawn(|| core_main(core, jvm, message_receiver));
let (components, component_control) = (computer.components.clone(), computer.component_control.clone()); }
let (started_up_sender, started_up_receiver) = oneshot::channel();
let (message_sender, message_receiver) = mpsc::channel(); fn core_main(core: Core, jvm: JavaVM, message_receiver: mpsc::Receiver<CoreMessage>) {
let memories = (computer.shared_ram.clone(), computer.shared_rom.clone()); let env = jvm.attach_current_thread().unwrap();
let mut uc = Unicorn::new_with_data(
Arch::ARM64,
Mode::LITTLE_ENDIAN,
CoreContext {
core,
env,
is_sleeping: true,
},
)
.unwrap();
std::thread::spawn({ let bus = uc.get_data().core.bus.clone();
let message_sender = message_sender.clone(); bus.map(&mut uc);
uc.add_intr_hook(interrupt_handler).unwrap();
uc
.add_mem_hook(
HookType::MEM_INVALID,
u64::MIN,
u64::MAX,
|vcpu, mem_type, address, _, value| {
eprintln!("memory exception: {mem_type:?} at {address:X} (value: {value:X})");
move || { vcpu.emu_stop().unwrap();
let env = java_vm.attach_current_thread_permanently().unwrap(); true
},
)
.unwrap();
let context = CpuContext { let try_fetch_message = |stopped| {
component_data_start: calculate_components_start(components.len()), if stopped {
components, message_receiver.recv().map(Some).map_err(|_| ())
component_control, } else {
message_sender, Ok(message_receiver.try_recv().ok())
component_access_futures: Vec::new(), }
ram: memories.0.clone(), };
_env: env,
stopped: true, let mut instructions_left = 0;
}; loop {
thread( while let Some(message) = try_fetch_message(uc.get_data().is_sleeping || instructions_left == 0).transpose() {
core_index, let Ok(message) = message else { return };
instructions_per_tick,
memories, match message {
context, CoreMessage::Tick => {
started_up_sender, if uc.get_data().is_sleeping {
message_receiver, continue;
) }
println!("ticking");
instructions_left = uc.get_data().core.instructions_per_tick as usize;
}
CoreMessage::Reset => {
let reset_address = uc.get_data().core.registers.read().reset_address.get();
println!("reset! address: {reset_address:08X}");
uc.set_pc(reset_address).expect("error while executing cpu")
}
CoreMessage::Wake => uc.get_data_mut().is_sleeping = false,
CoreMessage::Sleep => uc.get_data_mut().is_sleeping = true,
} }
}); }
println!("executing");
let _ = started_up_receiver.recv().expect("failed to start up core"); uc.emu_start(
uc.pc_read().expect("error while executing cpu"),
CoreHandle { message_sender } 0,
} 0,
instructions_left.min(1000),
#[unsafe(export_name = "Java_ca_sanae_golemcomputers_computer_RustNative_coreFree")] )
unsafe extern "C" fn free<'local>(_env: JNIEnv<'local>, core: *const Self) { .expect("error while executing cpu");
// Safety: the responsibility of verifying the pointer is valid is on the java caller instructions_left = instructions_left.saturating_sub(1000);
drop(unsafe { Arc::from_raw(core) })
}
pub fn tick(&self) {
self.message_sender.send(CpuMessage::Tick).expect("cpu thread panicked :(");
}
pub fn wake_at(&self, start: u64) {
self.message_sender.send(CpuMessage::Wake(Some(start))).unwrap();
} }
} }
@ -126,110 +145,29 @@ enum Exception {
Unaligned = 22, Unaligned = 22,
} }
fn thread( fn interrupt_handler(uc: &mut Unicorn<CoreContext>, kind: u32) {
core_index: usize, let exception = Exception::from_u32(kind).unwrap();
instructions_per_tick: u32,
(shared_ram, shared_rom): (Arc<UnsyncCell<[u8]>>, Arc<UnsyncCell<[u8]>>),
context: CpuContext,
started_up_sender: oneshot::Sender<()>,
message_receiver: mpsc::Receiver<CpuMessage>,
) -> Result<(), ()> {
let mut vcpu =
Unicorn::new_with_data(Arch::ARM64, Mode::LITTLE_ENDIAN, context).expect("failed to spawn unicorn engine");
const MPIDR_EL1: ArmCpRegister = ArmCpRegister::from_manual(0b11, 0b000, 0b0000, 0b0000, 0b101); let pc = uc.pc_read().unwrap();
vcpu.reg_write_cp(CP_REG, MPIDR_EL1, core_index as u64).unwrap(); println!("exception: {exception:?}");
let mut opcode = 0u32;
vcpu if let Ok(_) = uc.mem_read(pc, opcode.as_mut_bytes()) {
.mmio_map( if let Some(opcode) = decoder::decode(opcode) {
0x2000_0000, let mut inst = String::new();
0x1000, disarm64::format_insn::format_insn_pc(pc, &mut inst, &opcode).unwrap();
Some(component_mmio_read), println!("pc {pc:08X} {inst}");
Some(component_mmio_write),
)
.expect("failed to map mmio for core");
shared_rom
.map(&mut vcpu, 0x4000_0000, None, Permission::READ | Permission::EXEC)
.expect("failed to map memory for rom");
shared_ram.map(&mut vcpu, 0x8000_0000, None, Permission::ALL).expect("failed to map memory for rom");
use RegisterARM64::*;
vcpu
.add_intr_hook(|vcpu, excp| {
println!("interrupt: {excp}");
let exception = Exception::from_u32(excp).unwrap();
let pc = vcpu.pc_read().unwrap();
println!("exception: {exception:?}");
let mut opcode = 0u32;
if let Ok(_) = vcpu.mem_read(pc, opcode.as_mut_bytes()) {
if let Some(opcode) = decoder::decode(opcode) {
let mut inst = String::new();
disarm64::format_insn::format_insn_pc(pc, &mut inst, &opcode).unwrap();
println!("pc {pc:08X} {inst}");
} else {
println!("pc {pc:08X} - op parse failed {opcode:08X}");
}
} else {
println!("pc {pc:08X} - can't read");
}
println!("x0 {:08X}", vcpu.reg_read(X0).unwrap());
println!("x1 {:08X}", vcpu.reg_read(X1).unwrap());
let syndrome = vcpu.query(Query::SYNDROME).unwrap();
println!("0x{syndrome:08X}");
vcpu.get_data_mut().stopped = true;
vcpu.emu_stop().unwrap();
})
.unwrap();
vcpu
.add_mem_hook(
HookType::MEM_INVALID,
u64::MIN,
u64::MAX,
|vcpu, mem_type, address, _, value| {
eprintln!("memory exception: {mem_type:?} at {address:X} (value: {value:X})");
vcpu.emu_stop().unwrap();
true
},
)
.unwrap();
started_up_sender.send(()).expect("panicked when trying to core");
let try_fetch_message = |stopped| {
if stopped {
message_receiver.recv().map(Some).map_err(|_| ())
} else { } else {
Ok(message_receiver.try_recv().ok()) println!("pc {pc:08X} - op parse failed {opcode:08X}");
} }
}; } else {
println!("pc {pc:08X} - can't read");
let mut instructions_left = 0;
loop {
while let Some(message) = try_fetch_message(vcpu.get_data().stopped || instructions_left == 0)? {
match message {
CpuMessage::Tick => {
if vcpu.get_data().stopped {
continue;
}
}
CpuMessage::Wake(address) => {
if let Some(address) = address {
vcpu.set_pc(address).unwrap();
println!("waking at {address}");
}
vcpu.get_data_mut().stopped = false;
let pc = vcpu.pc_read().unwrap();
println!("executing {pc:X}");
vcpu.emu_start(pc, 0, 0, instructions_per_tick as usize).expect("error while executing cpu");
}
CpuMessage::Sleep => vcpu.get_data_mut().stopped = true,
}
}
vcpu.emu_start(vcpu.pc_read().unwrap(), 0, 0, instructions_left.min(1000)).expect("error while executing cpu");
instructions_left = instructions_left.saturating_sub(1000);
} }
use unicorn_engine::RegisterARM64::*;
println!("X0: {:X}", uc.reg_read(X0).unwrap());
println!("X1: {:X}", uc.reg_read(X1).unwrap());
println!("X2: {:X}", uc.reg_read(X2).unwrap());
// if an exception occurs, we don't want to execute anymore
uc.get_data_mut().is_sleeping = true;
} }

174
rust/src/device_bus.rs Normal file
View file

@ -0,0 +1,174 @@
use std::{num::NonZeroUsize, sync::Arc};
use bitfield_struct::bitfield;
use nodit::{interval::ie, Interval, NoditMap, OverlapError};
use unicorn_engine::{Permission, Unicorn};
use zerocopy::{little_endian::U32, FromBytes, FromZeros, Immutable, IntoBytes, KnownLayout};
use crate::{align_up_to_page, components::AllComponent, core::CoreContext, unsync_cell::UnsyncCell};
pub const COMPONENT_TABLE_START: u64 = 0x1000_0000;
pub const COMPONENTS_START: u64 = 0x2000_0000;
pub const ROM_START: u64 = 0x4000_0000;
pub const RAM_START: u64 = 0x8000_0000;
enum Device {
Rom(Box<UnsyncCell<[u8]>>),
Ram(Box<UnsyncCell<[u8]>>),
ComponentTable,
Component(AllComponent),
}
#[bitfield(u32)]
struct ComponentEntryHeader {
#[bits(31)]
_padding: u32,
filled: bool,
}
#[derive(Clone, Copy, FromBytes, IntoBytes, Immutable, KnownLayout)]
#[repr(C)]
struct ComponentTableEntry {
header: U32,
address: U32,
name: [u8; 0x30],
}
struct ComponentTable(Box<[u8]>);
pub struct DeviceBus {
devices: NoditMap<u64, Interval<u64>, Device>,
component_table: ComponentTable,
}
fn assert_non_overlapping(result: Result<(), OverlapError<Device>>) {
if result.is_err() {
panic!("insert overlapped!")
}
}
impl DeviceBus {
pub fn new(ram: Box<UnsyncCell<[u8]>>, rom: Box<UnsyncCell<[u8]>>, components: Vec<AllComponent>) -> Self {
let mut devices = NoditMap::new();
assert_non_overlapping(devices.insert_strict(ie(ROM_START, ROM_START + rom.len() as u64), Device::Rom(rom)));
assert_non_overlapping(devices.insert_strict(ie(RAM_START, RAM_START + ram.len() as u64), Device::Ram(ram)));
let mut component_table = {
let mut table = Box::new_uninit_slice(align_up_to_page(components.len() * size_of::<ComponentTableEntry>()));
table.zero();
unsafe { table.assume_init() }
};
assert_non_overlapping(devices.insert_strict(
ie(
COMPONENT_TABLE_START,
COMPONENT_TABLE_START + component_table.len() as u64,
),
Device::ComponentTable,
));
{
let component_table = <[ComponentTableEntry]>::mut_from_bytes_with_elems(
&mut component_table[..components.len() * size_of::<ComponentTableEntry>()],
components.len(),
)
.unwrap();
let mut component_offset = COMPONENTS_START;
for (index, component) in components.into_iter().enumerate() {
let start = component_offset;
let end = align_up_to_page(component_offset + component.memory_map_size());
println!("component offset: {start:X}..{end:X} and {}", u32::MAX);
assert!(end < ROM_START);
component_offset = end;
component_table[index] = ComponentTableEntry {
header: U32::new(ComponentEntryHeader::new().with_filled(true).into_bits()),
address: U32::new(start as u32),
name: component.name(),
};
assert_non_overlapping(devices.insert_strict(ie(start, end), Device::Component(component)));
}
}
Self {
devices,
component_table: ComponentTable(component_table),
}
}
pub fn map(self: Arc<Self>, uc: &mut Unicorn<CoreContext>) {
for (interval, device) in self.devices.iter() {
let (address, size) = (interval.start(), (interval.end() - interval.start()) as usize + 1);
println!("address, size {address:X}, {size:X} {:X?}", interval);
match device {
Device::Rom(memory) => unsafe {
uc.mem_map_ptr(
address,
size,
Permission::READ | Permission::WRITE,
memory.as_mut_slice_ptr() as _,
)
.unwrap();
},
Device::Ram(memory) => unsafe {
uc.mem_map_ptr(
address,
size,
Permission::READ | Permission::WRITE | Permission::EXEC,
memory.as_mut_slice_ptr() as _,
)
.unwrap();
},
Device::ComponentTable => unsafe {
println!("component table range {address:X} + {size:X}");
uc.mem_map_ptr(address, size, Permission::READ, self.component_table.0.as_ptr() as _).unwrap()
},
Device::Component(_) => {
fn calculate_size(component: &AllComponent, offset: u64, size: usize) -> Option<NonZeroUsize> {
if offset >= component.memory_map_size() {
return None;
}
let offset = offset as usize;
NonZeroUsize::new(usize::min(offset + size, component.memory_map_size() as usize) - offset)
}
let bus_read = self.clone();
let bus_write = self.clone();
uc.mmio_map(
address,
size,
Some(move |uc: &mut Unicorn<CoreContext>, offset: u64, size: usize| {
let Device::Component(component) = bus_read.devices.get_at_point(address).unwrap() else {
unreachable!("device was not a component")
};
let Some(size) = calculate_size(component, offset, size).map(NonZeroUsize::get) else {
return 0;
};
let mut data = 0u64;
component.read(uc.get_data_mut().get_env(), offset, &mut data.as_mut_bytes()[..size]);
data
}),
Some(
move |uc: &mut Unicorn<CoreContext>, offset: u64, size: usize, data: u64| {
let Device::Component(component) = bus_write.devices.get_at_point(address).unwrap() else {
unreachable!("device was not a component")
};
let Some(size) = calculate_size(component, offset, size).map(NonZeroUsize::get) else {
return;
};
component.write(uc.get_data_mut().get_env(), offset, &data.to_le_bytes()[..size]);
},
),
)
.unwrap()
}
}
}
}
}

View file

@ -1,6 +1,5 @@
#![deny(unsafe_op_in_unsafe_fn)] #![deny(unsafe_op_in_unsafe_fn)]
#![feature(sync_unsafe_cell)] #![feature(sync_unsafe_cell)]
#![feature(generic_const_exprs)]
use std::ops::Range; use std::ops::Range;
@ -8,12 +7,13 @@ use jni::{
sys::{jint, JNI_VERSION_1_8}, sys::{jint, JNI_VERSION_1_8},
JNIEnv, JNIEnv,
}; };
use num_traits::PrimInt;
pub mod component; pub mod components;
mod computer; mod computer;
mod core; pub mod core;
mod device_bus;
pub mod unsync_cell; pub mod unsync_cell;
mod memory_map;
#[macro_export] #[macro_export]
macro_rules! assert_positive { macro_rules! assert_positive {
@ -45,6 +45,30 @@ macro_rules! assert_positive {
}}; }};
} }
#[macro_export]
macro_rules! component_name {
($value: expr) => {{
const DATA: [u8; 0x30] = {
let name = { $value };
if name.len() >= 0x30 {
panic!("name length must be less than 64 characters")
}
let mut new_name = [0; 0x30];
let mut index = name.len();
loop {
index -= 1;
new_name[index] = name[index];
if index == 0 {
break;
}
}
new_name
};
DATA.clone()
}};
}
#[macro_export] #[macro_export]
macro_rules! range_of_field { macro_rules! range_of_field {
($type: ty, $field: ident) => {{ ($type: ty, $field: ident) => {{
@ -68,19 +92,19 @@ fn overlapping<Idx: PartialOrd>(a: &Range<Idx>, b: &Range<Idx>) -> bool {
a.contains(&b.start) && a.contains(&b.end) a.contains(&b.start) && a.contains(&b.end)
} }
fn subtract_range(a: &Range<usize>, b: &Range<usize>) -> Range<usize> { // fn subtract_range(a: &Range<usize>, b: &Range<usize>) -> Range<usize> {
a.start - b.start..a.end - b.end // a.start - b.start..a.end - b.end
// }
pub fn align_up_to_page<T: PrimInt>(value: T) -> T {
align_up(value, T::one().unsigned_shl(12))
} }
pub fn align_up(value: u64, alignment: u64) -> u64 { pub fn align_up<T: PrimInt>(value: T, alignment: T) -> T {
(value + alignment - 1) & !(alignment - 1) (value + alignment - T::one()) & !(alignment - T::one())
}
struct UnicornWrap {
} }
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
pub extern "system" fn JNI_OnLoad<'local>(_: JNIEnv<'local>, _: usize) -> jint { extern "system" fn JNI_OnLoad<'local>(_: JNIEnv<'local>, _: usize) -> jint {
JNI_VERSION_1_8 JNI_VERSION_1_8
} }

View file

@ -1,3 +0,0 @@
pub const COMPONENTS_START: u64 = 0x2000_0000;
pub const ROM_START: u64 = 0x4000_0000;
pub const RAM_START: u64 = 0x8000_0000;

View file

@ -2,7 +2,7 @@ use std::cell::SyncUnsafeCell;
use jni::{objects::JByteBuffer, JNIEnv}; use jni::{objects::JByteBuffer, JNIEnv};
use unicorn_engine::{uc_error, Permission, Unicorn}; use unicorn_engine::{uc_error, Permission, Unicorn};
use zerocopy::{FromBytes, FromZeros, Immutable, IntoBytes, KnownLayout}; use zerocopy::{FromBytes, FromZeros, IntoBytes, KnownLayout};
/// A simple replacement for [std::cell::Cell] in Sync contexts which explicitly does not try to synchronize the contents. /// A simple replacement for [std::cell::Cell] in Sync contexts which explicitly does not try to synchronize the contents.
/// This is used for contexts where unsynchronized access is explicitly fine. /// This is used for contexts where unsynchronized access is explicitly fine.
@ -19,6 +19,12 @@ impl<T> UnsyncCell<T> {
} }
} }
impl<T> UnsyncCell<[T]> {
pub fn as_mut_slice_ptr(&self) -> *mut [T] {
self.0.get()
}
}
impl<T: Sized + Copy> UnsyncCell<T> { impl<T: Sized + Copy> UnsyncCell<T> {
pub fn new(value: T) -> Self { pub fn new(value: T) -> Self {
Self(SyncUnsafeCell::new(value)) Self(SyncUnsafeCell::new(value))
@ -35,24 +41,36 @@ impl<T: Sized + Copy> UnsyncCell<T> {
impl<T: Sized + Copy + IntoBytes> UnsyncCell<T> { impl<T: Sized + Copy + IntoBytes> UnsyncCell<T> {
// N must be the same as the size of U // N must be the same as the size of U
pub fn read_into<U: FromBytes>(&self, offset: usize) -> U // pub fn read_into<U: FromBytes>(&self, offset: usize) -> U
where // where
[u8; size_of::<U>()]:, // [u8; size_of::<U>()]:,
{ // {
let mut buffer = [0; size_of::<U>()]; // let mut buffer = [0; size_of::<U>()];
let end = (offset + size_of::<U>()).min(size_of::<T>()); // let end = (offset + size_of::<U>()).min(size_of::<T>());
assert!(end - offset <= size_of::<U>()); // assert!(end - offset <= size_of::<U>());
// Safety: both pointers are valid for 0..(end - offset) // // Safety: both pointers are valid for 0..(end - offset)
unsafe { (self.as_mut_ptr() as *const u8).copy_to(buffer.as_mut_ptr(), end - offset) }; // unsafe { (self.as_mut_ptr() as *const u8).copy_to(buffer.as_mut_ptr(), end - offset) };
U::read_from_bytes(&buffer).expect("N was not the same as the size of U") // U::read_from_bytes(&buffer).expect("N was not the same as the size of U")
// }
pub fn read_into_byte_slice(&self, offset: usize, data: &mut [u8]) {
let end = (offset + data.len()).min(size_of::<T>());
assert!(end - offset <= data.len());
unsafe { (self.as_mut_ptr() as *const u8).copy_to(data.as_mut_ptr(), end - offset) };
} }
} }
impl<T: Sized + Copy + FromBytes> UnsyncCell<T> { impl<T: Sized + Copy + FromBytes> UnsyncCell<T> {
pub fn write_from<U: IntoBytes + Immutable>(&self, offset: usize, value: &U) { // pub fn write_from<U: IntoBytes + Immutable>(&self, offset: usize, value: &U) {
let end = (offset + size_of::<U>()).min(size_of::<T>()); // let end = (offset + size_of::<U>()).min(size_of::<T>());
assert!(end - offset <= size_of::<U>()); // assert!(end - offset <= size_of::<U>());
unsafe { (self.as_mut_ptr() as *mut u8).copy_from(value.as_bytes().as_ptr(), end - offset) }; // unsafe { (self.as_mut_ptr() as *mut u8).copy_from(value.as_bytes().as_ptr(), end - offset) };
// }
pub fn write_from_byte_slice(&self, offset: usize, data: &[u8]) {
let end = (offset + data.len()).min(size_of::<T>());
assert!(end - offset <= data.len());
unsafe { (self.as_mut_ptr() as *mut u8).copy_from(data.as_ptr(), end - offset) };
} }
} }
@ -69,7 +87,7 @@ impl<T: Sized + Copy> UnsyncCell<[T]> {
memory.zero(); memory.zero();
// Safety: UnsafeCell transparently wraps the slice, meaning the two types are semantically identical // Safety: UnsafeCell transparently wraps the slice, meaning the two types are semantically identical
unsafe { std::mem::transmute::<Box<[T]>, Box<Self>>(memory.assume_init()) } unsafe { std::mem::transmute::<Box<[T]>, Box<UnsyncCell<[T]>>>(memory.assume_init()) }
} }
fn as_mut_ptr(&self) -> *mut [T] { fn as_mut_ptr(&self) -> *mut [T] {
@ -80,6 +98,23 @@ impl<T: Sized + Copy> UnsyncCell<[T]> {
self.as_mut_ptr().len() self.as_mut_ptr().len()
} }
pub fn map<D>(
&self,
vcpu: &mut Unicorn<D>,
address: u64,
size: Option<usize>,
permissions: Permission,
) -> Result<(), uc_error> {
let size = size.unwrap_or(self.len());
if size > self.len() {
return Err(uc_error::ARG);
}
println!("mapping {permissions:?} at {address:08X} for {size:08X}");
unsafe { vcpu.mem_map_ptr(address, size, permissions, self.as_mut_ptr() as *mut _) }
}
}
impl UnsyncCell<[u8]> {
pub fn update_from_slice(&self, bytes: &[u8]) -> Result<(), usize> { pub fn update_from_slice(&self, bytes: &[u8]) -> Result<(), usize> {
if self.len() < bytes.len() { if self.len() < bytes.len() {
return Err(bytes.len() - self.len()); return Err(bytes.len() - self.len());
@ -107,21 +142,6 @@ impl<T: Sized + Copy> UnsyncCell<[T]> {
Ok(()) Ok(())
} }
pub fn map<D>(
&self,
vcpu: &mut Unicorn<D>,
address: u64,
size: Option<usize>,
permissions: Permission,
) -> Result<(), uc_error> {
let size = size.unwrap_or(self.len());
if size > self.len() {
return Err(uc_error::ARG);
}
println!("mapping {permissions:?} at {address:08X} for {size:08X}");
unsafe { vcpu.mem_map_ptr(address, size, permissions, self.as_mut_ptr() as *mut _) }
}
} }
impl<T: Clone + Copy> Clone for UnsyncCell<T> { impl<T: Clone + Copy> Clone for UnsyncCell<T> {

View file

@ -3,6 +3,7 @@ pluginManagement {
maven("https://maven.fabricmc.net/") { maven("https://maven.fabricmc.net/") {
name = "Fabric" name = "Fabric"
} }
gradlePluginPortal() gradlePluginPortal()
mavenCentral() mavenCentral()
} }

View file

@ -4,10 +4,12 @@ import ca.sanae.golemcomputers.GolemComputers
import com.mojang.serialization.MapCodec import com.mojang.serialization.MapCodec
import net.minecraft.block.BlockState import net.minecraft.block.BlockState
import net.minecraft.block.BlockWithEntity import net.minecraft.block.BlockWithEntity
import net.minecraft.block.CommandBlock
import net.minecraft.block.entity.BlockEntity import net.minecraft.block.entity.BlockEntity
import net.minecraft.block.entity.BlockEntityTicker import net.minecraft.block.entity.BlockEntityTicker
import net.minecraft.block.entity.BlockEntityType import net.minecraft.block.entity.BlockEntityType
import net.minecraft.entity.player.PlayerEntity import net.minecraft.entity.player.PlayerEntity
import net.minecraft.server.command.SayCommand
import net.minecraft.sound.BlockSoundGroup import net.minecraft.sound.BlockSoundGroup
import net.minecraft.text.Text import net.minecraft.text.Text
import net.minecraft.util.ActionResult import net.minecraft.util.ActionResult
@ -51,8 +53,9 @@ class GolemBlock :
if (world.isClient()) { if (world.isClient()) {
return ActionResult.SUCCESS return ActionResult.SUCCESS
} }
world.server!!.playerManager.broadcast(Text.literal("Hello world!"), false)
blockEntity.incrementClicks(); blockEntity.incrementClicks();
player.sendMessage(Text.literal("You've clicked the block for the ${blockEntity.getClicks()}th time."), true); // player.sendMessage(Text.literal("You've clicked the block for the ${blockEntity.getClicks()}th time."), true);
return ActionResult.SUCCESS_SERVER return ActionResult.SUCCESS_SERVER
} }

View file

@ -2,6 +2,7 @@ package ca.sanae.golemcomputers.blocks
import ca.sanae.golemcomputers.GolemComputers import ca.sanae.golemcomputers.GolemComputers
import ca.sanae.golemcomputers.computer.Computer import ca.sanae.golemcomputers.computer.Computer
import ca.sanae.golemcomputers.computer.components.ChatComponent
import net.fabricmc.api.EnvType import net.fabricmc.api.EnvType
import net.fabricmc.api.Environment import net.fabricmc.api.Environment
import net.minecraft.block.BlockState import net.minecraft.block.BlockState
@ -10,6 +11,8 @@ import net.minecraft.nbt.NbtCompound
import net.minecraft.registry.RegistryWrapper import net.minecraft.registry.RegistryWrapper
import net.minecraft.util.math.BlockPos import net.minecraft.util.math.BlockPos
import net.minecraft.world.World import net.minecraft.world.World
import java.nio.channels.FileChannel
import kotlin.io.path.Path
class GolemBlockEntity(pos: BlockPos?, state: BlockState?) : class GolemBlockEntity(pos: BlockPos?, state: BlockState?) :
BlockEntity(GolemComputers.GOLEM_BLOCK_ENTITY, pos, state) { BlockEntity(GolemComputers.GOLEM_BLOCK_ENTITY, pos, state) {
@ -34,7 +37,17 @@ class GolemBlockEntity(pos: BlockPos?, state: BlockState?) :
} }
override fun setWorld(world: World?) { override fun setWorld(world: World?) {
computer = if (!world!!.isClient) Computer(world) else null val path = Path("/home/aubrey/Projects/golem-software/assembly.bin")
val channel = FileChannel.open(path);
val buffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size())
if (!world!!.isClient) {
val computer = Computer.Builder(world, buffer, 1u)
.addChat(ChatComponent(world.server!!))
.addCore(1000)
.build()
computer.resetCore(0, true);
this.computer = computer;
}
super.setWorld(world) super.setWorld(world)
} }

View file

@ -1,62 +1,66 @@
package ca.sanae.golemcomputers.computer package ca.sanae.golemcomputers.computer
import ca.sanae.golemcomputers.computer.RustNative.Companion.cleaner import ca.sanae.golemcomputers.computer.NativeProtobuf.ChatComponentInit
import ca.sanae.golemcomputers.computer.NativeProtobuf.ComponentInit
import ca.sanae.golemcomputers.computer.NativeProtobuf.ComputerInit
import ca.sanae.golemcomputers.computer.NativeProtobuf.CoreComponentInit
import ca.sanae.golemcomputers.computer.RustNative.cleaner
import ca.sanae.golemcomputers.computer.components.ChatComponent import ca.sanae.golemcomputers.computer.components.ChatComponent
import ca.sanae.golemcomputers.computer.components.Component
import net.minecraft.world.World import net.minecraft.world.World
import java.nio.ByteBuffer import java.nio.ByteBuffer
import java.nio.channels.FileChannel import java.nio.channels.FileChannel
import java.nio.file.Files
import kotlin.io.path.Path import kotlin.io.path.Path
class Computer(val world: World) { class Computer private constructor(val world: World, init: ComputerInit, initObjects: Array<Any>) {
@Suppress("unused") @Suppress("unused")
private val address: Long = 0 private val address: Long = 0
init { init {
new(1, 0x1000, intArrayOf(0x10000)) new(init.toByteArray(), initObjects)
cleaner.register(this) { cleaner.register(this) {
free() free()
} }
val path = Path("/home/aubrey/Projects/golem-software/assembly.bin")
val channel = FileChannel.open(path);
val buffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size())
val wakeAddress = buffer.getUnsignedInt();
updateRomTest(buffer.rewind())
println("waking at 0x${wakeAddress.toString(16)}")
insertComponent(0, ChatComponent(this))
wakeCoreAt(0, wakeAddress)
} }
fun ByteBuffer.getUnsignedInt(): Long { private external fun new(toByteArray: ByteArray, initObjects: Array<Any>): Long
return Integer.reverseBytes(this.getInt()).toLong() and 0xFFFFFFFFL
}
fun insertComponent(coreIndex: Int, component: Component) {
insertComponentNative(
coreIndex,
component,
component.info.typeName.toString(),
component.info.protocol.toString(),
component.info.bytesPerTick,
component.info.version
)
}
private external fun new(componentCapacity: Int, memorySize: Int, coreIps: IntArray): Long
private external fun free() private external fun free()
private external fun updateRomTest(data: ByteBuffer) external fun resetCore(core: Int, wakeCore: Boolean)
private external fun wakeCoreAt(coreIndex: Int, address: Long)
private external fun insertComponentNative(
index: Int,
component: Component,
typeName: String,
protocol: String,
bytesPerTick: UInt,
version: UInt
)
external fun tick(); external fun tick();
class Builder(val world: World, rom: ByteBuffer, ramPages: UInt) {
private val objects: ArrayList<Any> = ArrayList()
private val builder: ComputerInit.Builder = ComputerInit.newBuilder()
init {
builder.setPrimaryRomIndex(addObject(rom))
builder.setPrimaryRamPages(ramPages.toInt())
}
private fun addObject(value: Any): Int {
val index = objects.size
objects.add(value)
return index
}
fun addCore(instructionsPerTick: Int): Builder {
builder.addComponents(
ComponentInit.newBuilder()
.setCore(CoreComponentInit.newBuilder().setInstructionsPerTick(instructionsPerTick).build())
)
return this
}
fun addChat(chat: ChatComponent): Builder {
builder.addComponents(
ComponentInit.newBuilder().setChat(ChatComponentInit.newBuilder().setJavaIndex(addObject(chat)).build())
)
return this
}
fun build(): Computer {
return Computer(world, builder.build(), objects.toArray())
}
}
} }

View file

@ -6,27 +6,24 @@ import java.lang.ref.Cleaner
import java.nio.channels.Channels import java.nio.channels.Channels
class RustNative private constructor() { object RustNative {
companion object { val cleaner: Cleaner = Cleaner.create()
val cleaner: Cleaner = Cleaner.create()
private val instance: RustNative = RustNative();
init { init {
val nativeLibraryName = System.mapLibraryName("golem_computers") val nativeLibraryName = System.mapLibraryName("golem_computers")
val tempFile: File = File.createTempFile("extracted_", nativeLibraryName) val tempFile: File = File.createTempFile("extracted_", nativeLibraryName)
val classLoader = RustNative::class.java.getClassLoader() val classLoader = RustNative::class.java.getClassLoader()
Channels.newChannel(classLoader.getResourceAsStream(nativeLibraryName)!!).use { src -> Channels.newChannel(classLoader.getResourceAsStream(nativeLibraryName)!!).use { src ->
FileOutputStream(tempFile).channel.use { dst -> FileOutputStream(tempFile).channel.use { dst ->
dst.transferFrom(src, 0, Long.MAX_VALUE) dst.transferFrom(src, 0, Long.MAX_VALUE)
}
} }
System.load(tempFile.absolutePath)
} }
System.load(tempFile.absolutePath)
}
fun isInitialized(): Boolean { fun isInitialized(): Boolean {
return true return true
}
} }
} }

View file

@ -1,29 +1,13 @@
package ca.sanae.golemcomputers.computer.components package ca.sanae.golemcomputers.computer.components
import ca.sanae.golemcomputers.GolemComputers import net.minecraft.server.MinecraftServer
import ca.sanae.golemcomputers.computer.Computer
import kotlinx.io.bytestring.decodeToString
import kotlinx.io.bytestring.getByteString
import net.minecraft.text.Text import net.minecraft.text.Text
import java.nio.ByteBuffer
class ChatComponent(private val computer: Computer) : class ChatComponent(val server: MinecraftServer) {
Component(GolemComputers.id("chat"), GolemComputers.id("string_stream"), 10000, 0u) { @Suppress("unused")
var chatBuffer: String = "" fun sendChatMessage(string: String) {
override fun gotByteChunk(buffer: ByteBuffer) { server.execute {
chatBuffer += buffer.getByteString().decodeToString() server.playerManager.broadcast(Text.of(string), true)
while (true) {
val index = chatBuffer.indexOf('\u0000')
if (index == -1) break
val (message, new) = chatBuffer.splitAtIndex(index)
chatBuffer = new
computer.world.server!!.sendMessage(Text.of(message))
} }
} }
override fun restarted() {
chatBuffer = ""
}
fun String.splitAtIndex(index: Int) = take(index) to substring(index)
} }

View file

@ -1,31 +0,0 @@
package ca.sanae.golemcomputers.computer.components
import com.github.pbbl.heap.ByteBufferPool
import com.jogamp.common.util.LFRingbuffer
import com.jogamp.common.util.Ringbuffer
import net.minecraft.util.Identifier
import java.nio.ByteBuffer
abstract class Component(val info: ComponentInfo) {
@Suppress("unused")
private val address: Long = 0
private val pool = ByteBufferPool()
abstract fun gotByteChunk(buffer: ByteBuffer)
private external fun writeBytesNative(buffer: ByteBuffer): Boolean
fun writeBytes(lambda: ((size: Int) -> ByteBuffer) -> ByteBuffer) {
synchronized(pool) {
val buffer = lambda(pool::take)
val overflowed = writeBytesNative(buffer)
pool.give(buffer)
if (overflowed) {
overflowShutdown()
}
}
}
// called when the component runs out of runway to store unread data
open fun overflowShutdown() {}
abstract fun restarted()
data class ComponentInfo(val typeName: Identifier, val protocol: Identifier, val bytesPerTick: UInt, val version: UInt)
}