Thursday, October 19, 2023
Fido2.NetFramework
Monday, August 28, 2023
Rust/WebAssembly vs Javascript performance on Mandelbrot animation rendering
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Mandelbrot</title>
<script type="text/javascript">
var cx = 0, cy = 0, kat1 = 0, kat2 = 1;
var WJulia = 1024;
var HJulia = 1024;
var frame = 0;
var startDate;
var contextJulia;
var contextSmall;
var pixJulia;
var imgdJulia;
var iStart;
var iEnd;
var jStart;
var jEnd;
var iDelta;
var jDelta;
function setInitialScale()
{
iStart = -1.92;
iEnd = 1.92;
jStart = -1.92;
jEnd = 1.92;
iDelta = (iEnd-iStart)/1024;
jDelta = (jEnd-jStart)/1024;
startDate = new Date();
}
function SetupMandel()
{
var elemJulia = document.getElementById('JuliaCanvas');
if (elemJulia && elemJulia.getContext)
{
contextJulia = elemJulia.getContext('2d');
if (contextJulia)
{
imgdJulia = contextJulia.createImageData(WJulia, HJulia);
pixJulia = imgdJulia.data;
}
}
setInitialScale();
/* start */
requestAnimationFrame( LoopMandel );
}
function LoopMandel()
{
kat1 += 0.0021;
kat2 += 0.0039;
cx = .681 * Math.sin(kat1);
cy = .626 * Math.cos(kat2);
frame++;
document.getElementById('fps').innerHTML = `FPS: ${1000*frame/(new Date()-startDate)}`;
RysujMandel();
requestAnimationFrame( LoopMandel );
}
/* tworzenie bitowego obrazu */
function RysujMandel()
{
/* obliczenia */
var wi, wj;
var i, j;
var iterations = 255;
var px = 0;
for (i = iStart, wi = 0; wi < 1024; i += iDelta, wi++)
{
var py = 0;
for (j = jStart, wj = 0; wj < 1024; j += jDelta, wj++)
{
var c = 0;
var x = cx;
var y = cy;
while (((x*x + y*y) < 4) && (c < iterations))
{
[x, y] = [x * x - y * y + i, 2 * x * y + j];
c++;
}
SetPixelColor( pixJulia, (py * WJulia + px) << 2, 255, 255-c, 255-c, 255 - (c/2) );
py++;
}
px++;
}
/* kopiowanie bitowego obrazu do context/canvas */
contextJulia.putImageData(imgdJulia, 0, 0);
}
function SetPixelColor(pix,offs, a, r, g, b)
{
pix[offs++] = r;
pix[offs++] = g;
pix[offs++] = b;
pix[offs] = a;
}
window.addEventListener( 'load', SetupMandel );
</script>
</head>
<body>
<span id="fps" style='display:block'></span>
<canvas id="JuliaCanvas" width="1024" height="1024">
</canvas>
</body>
</html>
// https://rustwasm.github.io/wasm-bindgen/examples/dom.html
use wasm_bindgen::prelude::*;
use wasm_bindgen::Clamped;
use web_sys::{CanvasRenderingContext2d, ImageData};
/* tworzenie bitowego obrazu */
#[wasm_bindgen]
pub fn mandel(
ctx: &CanvasRenderingContext2d,
width: u32, height: u32,
cx: f64, cy: f64) -> Result<(), JsValue> {
let i_start = -1.92;
let i_end = 1.92;
let j_start = -1.92;
let j_end = 1.92;
let i_delta = (i_end-i_start)/width as f64;
let j_delta = (j_end-j_start)/height as f64;
/* obliczenia */
let iterations = 255;
let mut pix_julia = Vec::with_capacity( usize::try_from(width * height).unwrap() );
let mut j = j_start;
for _wj in 0..height {
let mut i = i_start;
for _wi in 0..width {
let mut c: u8 = 0;
let mut x = cx;
let mut y = cy;
while ( (x*x + y*y) < 4.0) && (c < iterations)
{
let _tx = x;
x = x * x - y * y + i;
y = 2.0 * _tx * y + j;
c += 1;
}
pix_julia.push( 255-c );
pix_julia.push( 255-c );
pix_julia.push( 255-(c/2) );
pix_julia.push( 255 );
i += i_delta;
}
j += j_delta;
}
let data = ImageData::new_with_u8_clamped_array_and_sh(Clamped(&pix_julia), width, height)?;
ctx.put_image_data(&data, 0.0, 0.0)
}
[package] name = "hello-wasm" version = "0.1.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [lib] crate-type = ["cdylib"] [dependencies] wasm-bindgen = "0.2.87" [dependencies.web-sys] version = "0.3.4" features = [ 'Document', 'Element', 'HtmlElement', 'Node', 'Window', 'ImageData', 'CanvasRenderingContext2d' ] [profile.release] opt-level = 3 lto = true [package.metadata.wasm-pack.profile.release] wasm-opt = ["-O4", "--enable-mutable-globals"]
<!doctype html>
<html lang="en-US">
<head>
<meta charset="utf-8" />
<title>Mandelbrot</title>
</head>
<body>
<script type="module">
import init, { mandel } from "./pkg/hello_wasm.js";
(async function() {
await init();
SetupMandel();
})();
var cx = 0, cy = 0, kat1 = 0, kat2 = 1;
var WJulia = 1024;
var HJulia = 1024;
var frame = 0;
var startDate;
var contextJulia;
var contextSmall;
var pixJulia;
var imgdJulia;
function SetupMandel()
{
var elemJulia = document.getElementById('JuliaCanvas');
if (elemJulia && elemJulia.getContext)
{
contextJulia = elemJulia.getContext('2d');
if (contextJulia)
{
imgdJulia = contextJulia.createImageData(WJulia, HJulia);
pixJulia = imgdJulia.data;
}
}
startDate = new Date();
/* start */
requestAnimationFrame( LoopMandel );
}
function LoopMandel()
{
kat1 += 0.0021;
kat2 += 0.0039;
cx = .681 * Math.sin(kat1);
cy = .626 * Math.cos(kat2);
frame++;
document.getElementById('fps').innerHTML = `FPS: ${1000*frame/(new Date()-startDate)}`;
mandel(contextJulia, WJulia, HJulia, cx, cy);
requestAnimationFrame( LoopMandel );
}
</script>
<span id="fps" style='display: block'></span>
<canvas id="JuliaCanvas" width="1024" height="1024">
</canvas>
</body>
</html>
Thursday, July 20, 2023
Background music for coding - playlist
Rust binary tree with iterator
use std::fmt::Debug;
struct TreeNode<'a, T: 'a> {
val : &'a T,
left : Option<Box<TreeNode<'a, T>>>,
right: Option<Box<TreeNode<'a, T>>>
}
impl<'a, T> TreeNode<'a, T> {
fn new(
val: &'a T,
left: Option<Box<TreeNode<'a, T>>>,
right: Option<Box<TreeNode<'a, T>>>) -> Self {
Self { val, left, right }
}
}
/* C#'s IEnumerable on TreeNode - returns an iterator/enumerator */
impl<'a, T> IntoIterator for &'a TreeNode<'a, T> {
type Item = &'a TreeNode<'a, T>;
type IntoIter = TreeNodeIterator<'a, T>;
fn into_iter(self) -> Self::IntoIter {
TreeNodeIterator::init(&self)
}
}
/* C#'s IEnumerator */
struct TreeNodeIterator<'a, T: 'a> {
stack: Vec<&'a TreeNode<'a, T>>
}
impl<'a, T> TreeNodeIterator<'a, T> {
fn init(node: &'a TreeNode<'a, T>) -> Self {
Self { stack: vec![node] }
}
}
impl<'a, T> Iterator for TreeNodeIterator<'a, T> {
type Item = &'a TreeNode<'a, T>;
fn next(&mut self) -> Option<Self::Item> {
if self.stack.len() > 0 {
let curr = self.stack.pop()?;
/* one way to tackle the child noe */
if let Some(right) = &curr.right {
self.stack.push( right.as_ref() );
}
/* another way of doing the same */
if curr.left.is_some() {
self.stack.push(curr.left.as_ref().unwrap());
}
return Some(curr);
}
None
}
}
/* auxiliary code to be able to test the enumerator */
struct TreeNodeUtility;
impl TreeNodeUtility {
fn do_work<'a, T> ( node: &'a TreeNode<'a, T> ) where &'a T: Debug {
for n in node {
println!("{:?}", n.val );
}
}
}
fn main() {
let s1 = String::from("1");
let s2 = String::from("2");
let s3 = String::from("3");
let root = Some( TreeNode::<String>::new(
&s1,
Some(Box::new(TreeNode::<String>::new(
&s2,
None,
None
))),
Some(Box::new(TreeNode::<String>::new(
&s3,
None,
None
)))
) ) ;
if root.is_some() {
let rootc = root.unwrap();
TreeNodeUtility::do_work( &rootc );
TreeNodeUtility::do_work( &rootc );
}
}
- changed the type of the iterator's item to &T from &TreeNode<T>
- changed the TreeNodeIterator to a tupled struct (a struct with just a single field that doesn't even need a name)
- dropped the init method of the iterator struct
- changed the "if non empty stack - pop" code into a single liner
- changed the "if option is some - do" code into a single liner
use std::fmt::Debug;
struct TreeNode<'a, T: 'a> {
val : &'a T,
left : Option<Box<TreeNode<'a, T>>>,
right: Option<Box<TreeNode<'a, T>>>
}
impl<'a, T> TreeNode<'a, T> {
fn new(
val: &'a T,
left: Option<Box<TreeNode<'a, T>>>,
right: Option<Box<TreeNode<'a, T>>>) -> Self {
Self { val, left, right }
}
}
/* C#'s IEnumerable on TreeNode - returns an iterator/enumerator */
impl<'a, T> IntoIterator for &'a TreeNode<'a, T> {
type Item = &'a T;
type IntoIter = TreeNodeIterator<'a, T>;
fn into_iter(self) -> Self::IntoIter {
TreeNodeIterator(vec![self])
}
}
/* C#'s IEnumerator */
struct TreeNodeIterator<'a, T: 'a>(Vec<&'a TreeNode<'a, T>>);
impl<'a, T> Iterator for TreeNodeIterator<'a, T> {
type Item = &'a T;
fn next(&mut self) -> Option<Self::Item> {
if let Some(curr) = self.0.pop() {
/* one way to tackle the child node */
if let Some(right) = &curr.right {
self.0.push( right.as_ref() );
}
/* another way of doing the same */
if curr.left.is_some() {
self.0.push(curr.left.as_ref()?);
}
return Some(curr.val);
}
None
}
}
/* auxiliary code to be able to test the enumerator */
struct TreeNodeUtility;
impl TreeNodeUtility {
fn do_work<'a, T> ( node: &'a TreeNode<'a, T> ) where &'a T: Debug {
for n in node {
println!("{:?}", n );
}
}
}
fn main() {
let s1 = String::from("1");
let s2 = String::from("2");
let s3 = String::from("3");
let root = Some( TreeNode::<String>::new(
&s1,
Some(Box::new(TreeNode::<String>::new(
&s2,
None,
None
))),
Some(Box::new(TreeNode::<String>::new(
&s3,
None,
None
)))
) ) ;
if let Some( rootc ) = root {
TreeNodeUtility::do_work( &rootc );
TreeNodeUtility::do_work( &rootc );
}
}
Wednesday, July 5, 2023
Notes to myself-from-the-future
The more code I write over the years, the more additional non-code information I tend to leave to myself from the future. The additional information, put in inline comments, comments in signatures, external markdown files, is intended to be targeted exactly to me from the future.
It usually starts when I look for something. Like for example - an ASP.NET application, something that looks like it works upon each request but there's no visible sign of a public http module registered in web.config. Since that's odd, I go to the global.asax and try to find a module registered programmatically (rather than declaratively). And then I remember - "wait a minute, I remember myself searching for the same information some time ago".
This is where I make a conclusion - if I search for something and I remember searching for it at least once before, I try to think like myself-from-the-future. Where would I search for this information next time? Well, I am in global.asax now, so there's a high probability that the future-me will also come to the global.asax.
I decide to use the global.asax then and I leave a note to myself-from-the-future - If you search for [....], this information is in [....]"
Looking for various information, I then am grateful to myself-from-the-past - I often just stumble upon these "If you search for ... this information is in ...." which means that at some point in the past I tried to think like the present-me and my prediction at that time was correct. And often, I have no memory of writing these notes, I just see them in the code, I belive I wrote them at some point, I can even check the source control and find the exact date. But I just don't remember writing it at all.
All these notes I send to myself, from the past to the future, are nice and useful and somehow let me feel like the past-me and the present-me is for sure the very same me, despite the time that passes. Things change but the way I think about myself is solid.
Today was the day I tried to find something in the code. The code is an old desktop utility app that registers some HTTP listeners at start. There are multiple places listeners can be added, in the Main function, in the main form's constructor, in the main form's load event etc. At first I've assumed the myself-from-the-past would possibly leave a note to the present me somewhere.
I've checked few obvious places. Is there a global markdown file for such notes? Nope. Is there a comment in the main *.cs file? Nope. Is there a comment in the form constructor? Nope. Is there a comment above the form class definition? Yes, there is but the summary section doesn't seem to contain any information about registration of HTTP listeners.
So I've spent like 3 minutes to track down the registration. Found a method that does it. Found a reference to the method. Tracked the reference, until I've ended up in form's Load method.
Just leave a note to myself-from-the-future - I thought. Next time I will try to search for it, I will probably go to main form class and a comment above it. The place that already contains the summary section but lacks the information about listener registrations.
So I go there, go to the very bottom of the summary section, since it spans a dozen of lines I carefully navigate to its ending to put a new line with the new information about the registration of HTTP listeners.
And guess what. The summary section ends, the remarks section starts there, a section I obviously missed few minutes eariler (I just haven't noticed it's there, I thought the whole green block of comment lines was the summary section)!
And the only line in the remarks section is: If you search for registration of HTTP listeners, it's in frmMain_Load
I swear it was not there few minutes ago ... But the fact that I've spent few minutes doing something I thought I hadn't done before but ultimately looks like I had, is kind of reassuring.
Thursday, June 1, 2023
C# Puzzle No.25 (intermediate)
Let's have list of references to objects of a custom type that have a state. Let's loop through the list and alter the internal state of each object on the list.
Will the change of the internal state be preserved after the loop ends?
A first answer should of course be yes.
Take a look at this then:
var elements =
Enumerable.Range(0, 10)
.Select( i => new Foo() { Bar = i });
foreach ( var e in elements )
{
// yields 0,1,2,3,4,5,6,7,8,9 which is correct
Console.WriteLine( e.Bar );
}
foreach ( var e in elements )
{
e.Bar += 10;
// yields 10, 11, ...., 19
Console.WriteLine( e.Bar );
}
foreach ( var e in elements )
{
// yields 0,1,2,3,4,5,6,7,8,9 which doesn't seem correct
// (original values restored)!
Console.WriteLine( e.Bar );
// expected: 10, 11, ..., 19!
}
public class Foo
{
public int Bar { get; set; }
}
First we create a list of elements. Then we loop it to check if it's correct. Then we loop it to alter the internal state of objects.
Then we loop again and, oddly, it seems like original values are restored.
Not that obvious to spot what's wrong here, a similar issue was caught on production code. The goal is to find a simplest fix.
Monday, May 22, 2023
Unable to generate an explicit migration because the following explicit migrations are pending
- you have a parameterless DbContext's constructor and also a connection-string parameter constructor
- you generate migrations by adding the -ConnectionString and -ConnectionProviderName parameters of add-migration
- there's no default connection string in the configuration file
public class FooDbContext : DbContext
{
public FooDbContext() { } // this constructor is used by migration generator
public FooDbContext( string connectionString ) : base( connectionString ) { } // you could be using this from your code
}
