///|
priv struct SyncedSourceGeneration {
image_id : Int
generation : Int
}
///|
priv struct SourceImageCacheEntry {
image_id : Int
width : Int
height : Int
pixels_rgba8 : Array[Int]
}
///|
let synced_source_generations : Ref[Array[SyncedSourceGeneration]] = Ref([])
///|
let source_image_cache : Ref[Array[SourceImageCacheEntry]] = Ref([])
///|
fn normalized_size(value : Int) -> Int {
if value <= 0 {
1
} else {
value
}
}
///|
fn normalized_source_generation(generation : Int) -> Int {
if generation <= 0 {
1
} else {
generation
}
}
///|
fn synced_source_generation_at(image_id : Int) -> Int {
let mut generation = 0
for entry in synced_source_generations.val {
if entry.image_id == image_id {
generation = entry.generation
}
}
generation
}
///|
fn set_synced_source_generation(image_id : Int, generation : Int) -> Unit {
let next : Array[SyncedSourceGeneration] = []
let mut replaced = false
let safe_generation = normalized_source_generation(generation)
for entry in synced_source_generations.val {
if entry.image_id == image_id {
next.push({ image_id, generation: safe_generation, })
replaced = true
} else {
next.push(entry)
}
}
if !replaced {
next.push({ image_id, generation: safe_generation, })
}
synced_source_generations.val = next
}
///|
fn clear_synced_source_generations() -> Unit {
synced_source_generations.val = []
}
///|
fn drop_synced_source_generation(image_id : Int) -> Unit {
let next : Array[SyncedSourceGeneration] = []
for entry in synced_source_generations.val {
if entry.image_id != image_id {
next.push(entry)
}
}
synced_source_generations.val = next
}
///|
fn expected_rgba8_channel_count(width : Int, height : Int) -> Int {
normalized_size(width) * normalized_size(height) * 4
}
///|
fn has_valid_rgba8_payload(
rgba8_channels : Array[Int],
width : Int,
height : Int,
) -> Bool {
let expected = expected_rgba8_channel_count(width, height)
expected > 0 && rgba8_channels.length() >= expected
}
///|
fn clamp_u8_channel(value : Int) -> Int {
if value < 0 {
0
} else if value > 255 {
255
} else {
value
}
}
///|
fn normalized_rgba8_channels(
rgba8_channels : Array[Int],
width : Int,
height : Int,
) -> Array[Int] {
let expected = expected_rgba8_channel_count(width, height)
let out : Array[Int] = []
if expected <= 0 || rgba8_channels.length() < expected {
out
} else {
for i in 0.. SourceImageCacheEntry? {
let mut out : SourceImageCacheEntry? = None
for entry in source_image_cache.val {
if entry.image_id == image_id {
out = Some(entry)
}
}
out
}
///|
fn set_source_image_cache_entry(next_entry : SourceImageCacheEntry) -> Unit {
let next : Array[SourceImageCacheEntry] = []
let mut replaced = false
for entry in source_image_cache.val {
if entry.image_id == next_entry.image_id {
next.push(next_entry)
replaced = true
} else {
next.push(entry)
}
}
if !replaced {
next.push(next_entry)
}
source_image_cache.val = next
@engine.set_headless_texture_provider(() => {
source_image_cache.val.map(entry => {
id: entry.image_id,
width: entry.width,
height: entry.height,
pixels: entry.pixels_rgba8,
})
})
}
///|
fn clear_source_image_cache() -> Unit {
source_image_cache.val = []
}
///|
pub fn install(_canvas_selector : String) -> Unit {
()
}
///|
pub fn uninstall() -> Unit {
()
}
///|
pub fn is_ready() -> Bool {
false
}
///|
pub fn shutdown() -> Unit {
()
}
///|
pub fn register_source_image_palette(
source_image_id : Int,
_palette : @atlas.ImagePalette2x2,
) -> Unit {
if source_image_id < 0 {
return
}
let mut width = 1
let mut height = 1
let mut pixels_rgba8 : Array[Int] = []
match source_image_cache_entry_at(source_image_id) {
Some(entry) => {
width = entry.width
height = entry.height
pixels_rgba8 = entry.pixels_rgba8
}
None => ()
}
set_source_image_cache_entry({
image_id: source_image_id,
width,
height,
pixels_rgba8,
})
drop_synced_source_generation(source_image_id)
}
///|
pub fn register_source_image_solid_color(
source_image_id : Int,
color : @atlas.Rgba,
) -> Unit {
register_source_image_palette(
source_image_id,
@atlas.new_solid_palette(color),
)
}
///|
pub fn register_source_image_rgba8(
source_image_id : Int,
width : Int,
height : Int,
rgba8_channels : Array[Int],
) -> Unit {
if source_image_id < 0 {
return
}
let safe_width = normalized_size(width)
let safe_height = normalized_size(height)
let pixels_rgba8 = normalized_rgba8_channels(
rgba8_channels, safe_width, safe_height,
)
if pixels_rgba8.length() == 0 {
return
}
set_source_image_cache_entry({
image_id: source_image_id,
width: safe_width,
height: safe_height,
pixels_rgba8,
})
drop_synced_source_generation(source_image_id)
}
///|
pub fn patch_source_image_rgba8(
source_image_id : Int,
width : Int,
height : Int,
dirty_x : Int,
dirty_y : Int,
dirty_width : Int,
dirty_height : Int,
rgba8_channels : Array[Int],
) -> Bool {
if source_image_id < 0 {
return false
}
let safe_width = normalized_size(width)
let safe_height = normalized_size(height)
if !has_valid_rgba8_payload(rgba8_channels, safe_width, safe_height) {
return false
}
match source_image_cache_entry_at(source_image_id) {
Some(entry) =>
if entry.width != safe_width ||
entry.height != safe_height ||
!has_valid_rgba8_payload(entry.pixels_rgba8, safe_width, safe_height) {
false
} else {
let x0 = if dirty_x < 0 {
0
} else if dirty_x > safe_width {
safe_width
} else {
dirty_x
}
let y0 = if dirty_y < 0 {
0
} else if dirty_y > safe_height {
safe_height
} else {
dirty_y
}
let x1_raw = dirty_x + dirty_width
let y1_raw = dirty_y + dirty_height
let x1 = if x1_raw < 0 {
0
} else if x1_raw > safe_width {
safe_width
} else {
x1_raw
}
let y1 = if y1_raw < 0 {
0
} else if y1_raw > safe_height {
safe_height
} else {
y1_raw
}
if x1 <= x0 || y1 <= y0 {
false
} else {
let next_pixels = entry.pixels_rgba8
for y in y0.. false
}
}
///|
pub fn clear_source_image_palettes() -> Unit {
clear_source_image_cache()
clear_synced_source_generations()
}
///|
pub fn sync_source_image_palettes(
bindings : Array[@atlas.ImagePaletteBinding],
) -> Int {
let mut count = 0
for binding in bindings {
if binding.image_id >= 0 {
register_source_image_palette(binding.image_id, binding.palette)
count = count + 1
}
}
count
}
///|
pub fn sync_source_image_palettes_from_repository(
repository : @atlas.SimpleImageRepository,
) -> Int {
sync_source_image_palettes(@atlas.list_image_palette_bindings(repository))
}
///|
pub fn sync_source_images(bindings : Array[@atlas.SourceImageBinding]) -> Int {
let mut count = 0
for binding in bindings {
if binding.image_id < 0 {
continue
}
let generation = normalized_source_generation(binding.generation)
if synced_source_generation_at(binding.image_id) == generation {
continue
}
let required_channels = expected_rgba8_channel_count(
binding.width,
binding.height,
)
if required_channels > 0 &&
binding.pixels_rgba8.length() >= required_channels {
let mut patched = false
match binding.dirty_rect {
Some(rect) => {
let full_rect = rect.x <= 0 &&
rect.y <= 0 &&
rect.width >= normalized_size(binding.width) &&
rect.height >= normalized_size(binding.height)
if !full_rect {
patched = patch_source_image_rgba8(
binding.image_id,
binding.width,
binding.height,
rect.x,
rect.y,
rect.width,
rect.height,
binding.pixels_rgba8,
)
}
}
None => ()
}
if !patched {
register_source_image_rgba8(
binding.image_id,
binding.width,
binding.height,
binding.pixels_rgba8,
)
}
} else {
register_source_image_palette(binding.image_id, binding.palette)
}
set_synced_source_generation(binding.image_id, generation)
count = count + 1
}
count
}
///|
pub fn sync_source_images_from_repository(
repository : @atlas.SimpleImageRepository,
) -> Int {
sync_source_images(@atlas.list_source_image_bindings(repository))
}
///|
pub fn sync_dirty_source_images_from_repository(
repository : @atlas.SimpleImageRepository,
) -> Int {
let count = sync_source_images(
@atlas.list_dirty_source_image_bindings(repository),
)
let _ = @atlas.clear_source_image_dirty_flags(repository)
count
}
///|
pub fn sync_dirty_atlas_pages_from_repository(
repository : @atlas.SimpleAtlasImageRepository,
) -> Int {
let count = sync_source_images(
@atlas.list_dirty_atlas_page_bindings(repository),
)
let _ = @atlas.clear_dirty_atlas_page_flags(repository)
count
}
///|
pub fn source_image_cache_count() -> Int {
source_image_cache.val.length()
}
///|
pub fn source_image_generation(image_id : Int) -> Int {
synced_source_generation_at(image_id)
}
///|
pub fn source_image_width(image_id : Int) -> Int {
match source_image_cache_entry_at(image_id) {
Some(entry) => entry.width
None => 0
}
}
///|
pub fn source_image_height(image_id : Int) -> Int {
match source_image_cache_entry_at(image_id) {
Some(entry) => entry.height
None => 0
}
}
///|
pub fn source_image_pixel_channel(
image_id : Int,
x : Int,
y : Int,
channel_index : Int,
) -> Int {
if channel_index < 0 || channel_index > 3 {
return -1
}
match source_image_cache_entry_at(image_id) {
Some(entry) =>
if x < 0 || y < 0 || x >= entry.width || y >= entry.height {
-1
} else {
let base = (y * entry.width + x) * 4
let offset = base + channel_index
if offset < 0 || offset >= entry.pixels_rgba8.length() {
-1
} else {
entry.pixels_rgba8[offset]
}
}
None => -1
}
}
///|
pub fn request_animation_frame(_f : () -> Unit) -> Unit {
()
}
///|
fn init {
@native_services.install()
@engine.set_lifecycle_hooks_if_absent({
on_start: fn(_canvas, _title) { () },
on_stop: fn() { () },
})
}