Sr. Content Developer at Microsoft, working remotely in PA, TechBash conference organizer, former Microsoft MVP, Husband, Dad and Geek.
150989 stories
·
33 followers

Vektrix: Building a psychedelic twinstick shooter with WebGPU in 2 days

1 Share

You can play the game online.

I was looking for a twin stick shooter and nothing quite scratched the itch — nothing in my Steam library anyway. I fired up Geometry Wars, enjoyed it, but it still wasn’t quite what I wanted. What it did give me was the spark to build my own.

I’ve always loved the vector aesthetic (Rez remains a favourite), and I knew I only had a couple of days — in total, perhaps a day and a couple of early mornings — so whatever I built had to be simple, fast, and focused.

The game can be played online or the video below will give you a taste of what I ended up with:

Although it’s still fairly new I’ve done a reasonable amount with WebGPU (and quite a bit with Metal, WebGL and a spot of Vulkan) so that was the obvious choice for rendering. Futzing around with the clunky WebGL interface isn’t fun, and with WebGPU I figured I could handle the particle effects entirely on the GPU in a compute shader.

To accelerate development — and because even modern APIs like WebGPU involve a lot of boilerplate — I used Claude Code for a significant portion of the scaffolding.

If you’ve seen my YouTube series on building an 8-bit virtual console, you’ll recognise my approach: spec first, review critically, and iterate fast. That worked particularly well here.

In fact it supported a real fast iteration speed which is ideal for game development where things are very much about the “feel”. I got the grid up and running fast and from there I built out player movement and eventually enemies which is where the fun really began.

As I worked my way through gameplay concepts I solidified on the idea that the game was about relentless pressure - the iteration speed helped me get there fast. I tried and discounted a bunch of things:

  • Mines - too strategic
  • Black holes / gravity wells - slowed things down, though these got repurposed into mass spawners
  • Shields - too defensive

Basically if it slowed things down or didn’t serve the intensity and pressure directly in some way I discarded it. The sheer lack of sunk cost made it really easy to do this without that nagging sense of “well this took 3 days… maybe I should keep it” and I ended up with a stripped back game that’s all about pressure and chaos.

From the start I’d set off wanting to lean into an almost psychedelic vibe and as I worked on the game I ramped this up, dialled it back in areas, ramped it up again trying to pitch things just on the right side of comprehendible. Mostly I dialled it up! There was a lot of tweaking of bloom, of the fairly subtle CRT effect, and the phosphor trails. Then tinkering with particles - volume, speed, etc. WebGPU really helped with this - the particles running on the GPU leaves loads of room on the CPU for a high entity count and collisions etc.

I set up the data using an array based entity system that will play nicely with web workers. My original intention was to run some parts of the engine on workers but I’ve had no need: things seem to absolutely scream on my MacBook and gaming PC - though both are pretty powerful bits of kit and so I might need to do some tuning if people play this on lower end kit (I have none readily to hand).

The music came from Pixabay which fortunately has a large selection of permissively licensed dance tracks. The sound effects are just procedurally generated.

So what’s next? The endgame ramps a bit too aggressively and I think I could pace that better with some more enemy types. If people play it and find it struggling on lower end kit I might spend some time on optimisation. Perhaps spend some time on the sound effects… they are still a bit anaemic.

The source code for this project can be found on GitHub here.

Read the whole story
alvinashcraft
5 minutes ago
reply
Pennsylvania, USA
Share this story
Delete

Write cleaner code with C# 14's null-conditional assignment operator

1 Share
ℹ️
This post was originally published on the Telerik Developer Blog.

When looking at the C# 14 updates coming out soon, I discovered a feature and thought, "how did this not already exist?" If you've been using C#'s null-conditional operators (?. and ?[]) for years like I have, you'll love the support for null-conditional assignments.

Our long null journey

It's been a long journey to improve how C# developers work with null, the billion-dollar mistake.

C# 2 kicked things off with nullable value types (like int? and bool?) because sometimes you need to know if someone didn't enter a number versus entering zero. With C# 6, we got null-conditional operators (?. and ?[]), letting us chain through potentially null objects without writing novels. C# 7 gave us pattern matching with is null checks that read like English. C# 8 came out with nullable reference types, the null-coalescing operator (??=), and the null-forgiving operator (!) for when you think you know better than the compiler. And, of course, C# 9 rounded it out with is not null because is null was feeling lonely.

For me, the null-conditional operators from C# 6 were a game-changer. Instead of doom-checking each level of potential nulls, I can just chain it.

// The old way
string? city = null;
if (customer != null && customer.Address != null)
{
    city = customer.Address.City;
}

// The new way
string? city = customer?.Address?.City;

This was great for reading values. However, we could never use the same trick for writing values. Enter C# 14.

The current problem

How many times have you written code like this?

if (customer != null)
  customer.Order = GetOrder(customer.Id)

If you're anything like me (lucky you), this pattern is burned into your memory. I type it without thinking. But we already have a perfectly good "do this thing if not null" operator. We just couldn't use it for assignments. Weird, yes? We could read through each null conditionally but not write through it. Every little assignment needed its own little null check guard.

The C# 14 solution

C# 14 lets you write this instead:

customer?.Order = GetOrder(customer.Id);

That's it! Clean, readable, and the intent is obvious: "If the customer isn't null, assign the current order to it."

The semantics are exactly what you'd hope for: the right-hand side (GetOrder(id)) only runs if the left-hand side isn't null. If customer is null, nothing happens — no assignment, no method call, and no exceptions.

⚠️
Like any language feature, this is a tool and not a hammer for every nail. There are times when explicit null checks are actually clearer.

For example, don't hide business logic. If null requires specific handling, explicit checks are much clearer.

// Less clear - what should happen if account is null?
account?.Balance += deposit;

// Better when null is exceptional
if (account is null)
    throw new InvalidOperationException("Cannot deposit to null account");
    
account.Balance += deposit;

Watch out for side effects! Remember, the entire right-hand side is skipped if the left is null.

// We don't call GetNextId() if the record is null
record?.Id = GetNextId();

Compound assignments

The real power in this improvement shows up when you use null-conditional assignments with compound assignment operators.

// Before C# 14
if (account != null)
{
    account.Balance += deposit;
}

// C# 14
account?.Balance += deposit;

This works with all compound assignment operators: +=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>=, and ??=.

Check out this shopping cart example:

public class ShoppingCart
{
    public decimal Subtotal { get; set; }
    public decimal Tax { get; set; }
}

public void ApplyDiscount(ShoppingCart? cart, decimal discountAmount)
{
    // Only apply discount if cart exists
    cart?.Subtotal -= discountAmount;
    
    // Recalculate tax based on new subtotal
    cart?.Tax = cart.Subtotal * 0.08m;
}

Notice the improvements: no nested ifs, no ceremony, and just the expected "if it's there, update it" logic. Finally.

When this feature shines

Let's take a look at some brief real-world scenarios where null-conditional assignments are genuinely useful.

Optional dependencies

In modern apps, you have services everywhere — like logging, telemetry, caching, and so on — and it seems half of them are optional depending on what features are enabled.

public class TelemetryService
{
    private ILogger? _logger;
    private IMetricsCollector? _metrics;
    
    public void RecordEvent(string eventName, Dictionary<string, object> properties)
    {
        // Log only if logger is configured
        _logger?.LogInformation("Event recorded: {EventName}", eventName);
        
        // Track metrics only if collector is available
        _metrics?.EventCount += 1;
    }
}

With this improvement, our code doesn't get buried under a mountain of if (_logger != null) checks. The dependency is handled right where you use it, which means the happy path (the service is there) stays front and center. This is huge with classes that have multiple optional dependencies. Traditional null checks create a ton of noise that obscures what your code actually does.

Event handlers with optional subscribers

When you're working with events, subscribers are optional by nature. It's like me watching a football game: sometimes I'm listening and sometimes I'm not. And that's fine.

public class ProgressTracker
{
    public IProgress<int>? Progress { get; set; }
    private int _currentStep;
    private int _totalSteps;
    
    public void AdvanceProgress()
    {
        _currentStep++;
        var percentComplete = (_currentStep * 100) / _totalSteps;
        
        // Report only if someone is listening
        Progress?.Report(percentComplete);
    }
}

With this improvement, publishers don't need to defensively write null checks before every notification. This is great for library code or reusable components where you have zero control over whether consumers attach handlers. Plus, it's self-documenting. Progress?.Report() clearly says "if anyone cares, report progress."

Conditional updates with fluent APIs

Builder patterns and fluent APIs are obsessed with optional configuration. Sometimes you've set up all the pieces, and sometimes just a few.

public class ApplicationBuilder
{
    public DatabaseConfiguration? Database { get; set; }
    public ApiConfiguration? Api { get; set; }
    
    public void ApplyProduction()
    {
        // Safely configure only what exists
        Database?.ConnectionString = Environment.GetEnvironmentVariable("DB_PROD");
        Database?.CommandTimeout += TimeSpan.FromSeconds(30);
        
        Api?.RateLimitPerMinute = 1000;
        Api?.EnableCaching = true;
    }
}

With this example, configuration methods can be flexible without requiring everything to exist first. This is perfect for plugin architectures or systems where features come and go. You can write configuration code that gracefully handles a mix of initialized components without the spaghetti code.

Collection operations

We consistently work with collections that might not be initialized yet. Think of when you're doing lazy initialization for performance reasons.

public class CacheManager
{
    private List<string>? _recentItems;
    private Dictionary<string, object>? _settings;
    
    public void RecordAccess(string item)
    {
        // Add to recent items if cache is initialized
        _recentItems?.Add(item);
        
        // Update access count
        if (_settings?.ContainsKey("accessCount") == true)
        {
            _settings["accessCount"] = ((int)_settings["accessCount"]) + 1;
        }
    }
    
    public void UpdateTheme(string theme)
    {
        // Safe dictionary assignment
        _settings?.["theme"] = theme;
    }
}

Nice, eh? You can now work with collections that might not exist yet without checking if they exist first. This is great for performance-sensitive code where you want to defer allocating collections until you actually need them ... but you also want clean code. We can now keep the lazy initialization pattern without our code looking like a mess.

One gotcha: No ++ or –-

Don't kill the messenger: you can't use increment (++) or decrement (--) operators with null-conditional access.

// Ope
counter?.Value++;

If you want this pattern, do the traditional null check or use the compound assignment form.

counter?.Value += 1; 

Captain Obvious says: ++ and -- are a read and write operation rolled into one. And that's where semantics can get really weird with null-conditional operators. If counter is null, what should counter?.Value++ return? Null? The pre-increment value? The post-increment value that never computed? Instead of confusing everyone, it just isn't supported.

A "putting it all together" example: a configuration system

Let's put this all together with an example. Let's build a super-exciting configuration system that applies environment-specific overrides to application settings.

public class AppSettings
{
    public DatabaseConfig? Database { get; set; }
    public ApiConfig? Api { get; set; }
    public LoggingConfig? Logging { get; set; }
    public CacheConfig? Cache { get; set; }
}

public class DatabaseConfig
{
    public string? ConnectionString { get; set; }
    public int CommandTimeout { get; set; }
    public bool EnableRetry { get; set; }
}

public class ApiConfig
{
    public string? Endpoint { get; set; }
    public TimeSpan Timeout { get; set; }
    public int MaxRetries { get; set; }
}

public class ConfigurationUpdater
{
    public void ApplyEnvironmentOverrides(AppSettings? settings, Environment env)
    {
        // Database overrides - only if database config exists
        settings?.Database?.ConnectionString = env.GetVariable("DB_CONNECTION");
        settings?.Database?.CommandTimeout = 60;
        settings?.Database?.EnableRetry = true;
        
        // API configuration - compound assignments work too
        settings?.Api?.Endpoint = env.GetVariable("API_URL");
        settings?.Api?.Timeout += TimeSpan.FromSeconds(30);
        settings?.Api?.MaxRetries = 5;
        
        // Logging adjustments
        settings?.Logging?.Level = LogLevel.Information;
        settings?.Logging?.EnableConsole = env.IsDevelopment();
        
        // Cache settings
        settings?.Cache?.ExpirationMinutes += 15; 
    }
    
    public void ApplyDevelopmentDefaults(AppSettings? settings)
    {
        // Development-specific configuration
        settings?.Database?.EnableRetry = false; // Fail fast in dev
        settings?.Api?.Timeout = TimeSpan.FromSeconds(5); // Shorter timeouts
        settings?.Logging?.Level = LogLevel.Debug;
    }
}

This allows us to be modular. Not every app needs every config section. A simple POC might only touch database config, while our behemoth needs everything.

It also allows us to be optional by nature. The settings object itself might be null during early startup and individual sections might not be wired up yet. It be like that sometimes.

Compare it to the old way. To avert your eyes, I'll use a snippet.

// The verbose alternative (please don't do this)
if (settings != null)
{
    if (settings.Database != null)
    {
        settings.Database.ConnectionString = env.GetVariable("DB_CONNECTION");
        settings.Database.CommandTimeout = 60;
    }
    
    if (settings.Api != null)
    {
        settings.Api.Endpoint = env.GetVariable("API_URL");
        settings.Api.Timeout += TimeSpan.FromSeconds(30);
    }
}

That is a lot of ceremony just for "configure what exists." With null-conditional assignments, we get clean code that focuses on what we're configuring instead of whether we can configure it.

Wrapping up

Unless you're a language designer, C# 14's null-conditional assignment won't blow your mind. However, it will make your code clearer and easier to write. It takes a pattern we've all typed a thousand times and finally makes it as concise as it should have been all along.

So next time you catch yourself typing if (x != null) { x.Property = value; }, know there's a better way.

What patterns will you use with null-conditional assignments? Let me know in the comments, and happy coding!



Read the whole story
alvinashcraft
5 minutes ago
reply
Pennsylvania, USA
Share this story
Delete

#530: anywidget: Jupyter Widgets made easy

1 Share
For years, building interactive widgets in Python notebooks meant wrestling with toolchains, platform quirks, and a mountain of JavaScript machinery. Most developers took one look and backed away slowly. Trevor Manz decided that barrier did not need to exist. His idea was simple: give Python users just enough JavaScript to unlock the web’s interactivity, without dragging along the rest of the web ecosystem. That idea became anywidget, and it is quickly becoming the quiet connective tissue of modern interactive computing. Today we dig into how it works, why it has taken off, and how it might change the way we explore data.

Episode sponsors

Seer: AI Debugging, Code TALKPYTHON
PyCharm, code STRONGER PYTHON
Talk Python Courses

Trevor on GitHub: github.com

anywidget GitHub: github.com
Trevor's SciPy 2024 Talk: www.youtube.com
Marimo GitHub: github.com
Myst (Markdown docs): mystmd.org
Altair: altair-viz.github.io
DuckDB: duckdb.org
Mosaic: uwdata.github.io
ipywidgets: ipywidgets.readthedocs.io
Tension between Web and Data Sci Graphic: blobs.talkpython.fm
Quak: github.com
Walk through building a widget: anywidget.dev
Widget Gallery: anywidget.dev
Video: How do I anywidget?: www.youtube.com

PyCharm + PSF Fundraiser: pycharm-psf-2025 code STRONGER PYTHON

Watch this episode on YouTube: youtube.com
Episode #530 deep-dive: talkpython.fm/530
Episode transcripts: talkpython.fm

Theme Song: Developer Rap
🥁 Served in a Flask 🎸: talkpython.fm/flasksong

---== Don't be a stranger ==---
YouTube: youtube.com/@talkpython

Bluesky: @talkpython.fm
Mastodon: @talkpython@fosstodon.org
X.com: @talkpython

Michael on Bluesky: @mkennedy.codes
Michael on Mastodon: @mkennedy@fosstodon.org
Michael on X.com: @mkennedy




Download audio: https://talkpython.fm/episodes/download/530/anywidget-jupyter-widgets-made-easy.mp3
Read the whole story
alvinashcraft
12 hours ago
reply
Pennsylvania, USA
Share this story
Delete

Building a Frontend Before the API is Ready: Without Brittle Fixtures

1 Share
Building a Frontend Before the API is Ready: Without Brittle Fixtures
Read the whole story
alvinashcraft
12 hours ago
reply
Pennsylvania, USA
Share this story
Delete

F# Weekly #50, 2025 – Making of A Programming Language

1 Share

Welcome to F# Weekly,

A roundup of F# content from this past week:

News

🚀 Excited to announce SharpIDE – A Modern, Cross-Platform IDE for .NET!I'm thrilled to share my latest open-source project, just in time for .NET 10: SharpIDE, a brand new IDE for .NET, built with .NET and Godot! 🎉🔗 Check it out on GitHub: github.com/MattParkerDe……

Matt Parker (@mattparker.dev) 2025-11-11T23:24:13.521Z

Videos

Let's build a Programming Language – together!The goal? Combine the developer experience of Python with the safety of TypeScript.In Episode 0: Syntax + Type Inference📺 http://www.youtube.com/watch?v=fSRT…#TypeInference #TypeScript #CSharp #FSharp #Rust #Haskell

SchlenkR (@schlenkr.bsky.social) 2025-12-13T08:58:15.904Z

Blogs & FsAdvent

F# vNext

Highlighted projects

New Releases

That’s all for now. Have a great week.

If you want to help keep F# Weekly going, click here to jazz me with Coffee!

Buy Me A Coffee





Read the whole story
alvinashcraft
13 hours ago
reply
Pennsylvania, USA
Share this story
Delete

Rust in Linux's Kernel 'is No Longer Experimental'

1 Share
Steven J. Vaughan-Nichols files this report from Tokyo: At the invitation-only Linux Kernel Maintainers Summit here, the top Linux maintainers decided, as Jonathan Corbet, Linux kernel developer, put it, "The consensus among the assembled developers is that Rust in the kernel is no longer experimental — it is now a core part of the kernel and is here to stay. So the 'experimental' tag will be coming off." As Linux kernel maintainer Steven Rosted told me, "There was zero pushback." This has been a long time coming. This shift caps five years of sometimes-fierce debate over whether the memory-safe language belonged alongside C at the heart of the world's most widely deployed open source operating system... It all began when Alex Gaynor and Geoffrey Thomas at the 2019 Linux Security Summit said that about two-thirds of Linux kernel vulnerabilities come from memory safety issues. Rust, in theory, could avoid these by using Rust's inherently safer application programming interfaces (API)... In those early days, the plan was not to rewrite Linux in Rust; it still isn't, but to adopt it selectively where it can provide the most security benefit without destabilizing mature C code. In short, new drivers, subsystems, and helper libraries would be the first targets... Despite the fuss, more and more programs were ported to Rust. By April 2025, the Linux kernel contained about 34 million lines of C code, with only 25 thousand lines written in Rust. At the same time, more and more drivers and higher-level utilities were being written in Rust. For instance, the Debian Linux distro developers announced that going forward, Rust would be a required dependency in its foundational Advanced Package Tool (APT). This change doesn't mean everyone will need to use Rust. C is not going anywhere. Still, as several maintainers told me, they expect to see many more drivers being written in Rust. In particular, Rust looks especially attractive for "leaf" drivers (network, storage, NVMe, etc.), where the Rust-for-Linux bindings expose safe wrappers over kernel C APIs. Nevertheless, for would-be kernel and systems programmers, Rust's new status in Linux hints at a career path that blends deep understanding of C with fluency in Rust's safety guarantees. This combination may define the next generation of low-level development work.

Read more of this story at Slashdot.

Read the whole story
alvinashcraft
14 hours ago
reply
Pennsylvania, USA
Share this story
Delete
Next Page of Stories