Myoddweb.DirectoryWatcher 0.2.0: six years on, still watching

It has been six years since the last release of Myoddweb.DirectoryWatcher, my high-performance, asynchronous file and directory watcher for .NET and C++. Version 0.1.9 went out in August 2020, and then, as these things go, life happened. The library kept quietly doing its job in a couple of my own projects, but it didn’t get the attention it deserved.

This week I finally sat down and gave it the update it was owed. Version 0.2.0 is out now, both on GitHub and on NuGet.

Why this library exists

If you’ve ever used .NET’s built-in FileSystemWatcher, you’ll know it’s… fine, until it isn’t. It has a fixed internal buffer that silently overflows under heavy load, it happily throws exceptions that can bring your whole app down, and it really doesn’t enjoy being pointed at a large volume or a UNC path.

Myoddweb.DirectoryWatcher was built to fix exactly that:

  • Non-blocking, asynchronous events — a slow consumer never stalls the watcher.
  • No buffer overflow crashes, even under bursts of file activity.
  • Duplicate suppression, so three rapid writes to the same file become one clean notification instead of three.
  • Volume-wide monitoring of creates, deletes, touches and renames, across multiple paths at once.
  • Built-in statistics and logging, so you can see throughput and event rates without instrumenting it yourself.

Under the hood it’s a native C++ core built on Win32’s ReadDirectoryChangesW, wrapped in a clean, platform-agnostic managed interface. That split is what let this update focus so heavily on the native side without touching the public API.

What’s new in 0.2.0

This release is best described as “getting the house back in order” — it’s mostly stability, correctness and modernisation, not new features. But there’s a lot of it. A few highlights from the full changelog:

Toolchain caught up to 2026:

  • Solution and projects now build with Visual Studio 2022 (toolset v143).
  • The managed libraries target .NET Framework 4.6.2, .NET Standard 2.0 and .NET 8.0 — the old .NET Framework 4.5.2 and .NET Core 3.0 targets, both long out of support, are gone.
  • Tests moved to NUnit 4.6.1 and Google Test 1.18.0.
  • A GitHub Actions workflow replaces the now-retired Travis CI build.

A genuine bug fix, not just busywork:

  • Issue #20 — files could be silently missed when a whole populated folder was copy-pasted into a watched tree. That’s now fixed and covered by a new test.

A pile of native-code hardening:

  • An intermittent WorkerPool deadlock that could hang indefinitely.
  • A race condition in MonitorsManager::ready() for recursive monitors, where child monitors could report readiness before they’d actually started.
  • A use-after-free and a null-pointer dereference in the Windows-specific win::Data handling.
  • win::Data::process_error() no longer leaves a watched directory’s read loop silently stalled after a Win32 error.
  • A data race between a monitor’s in-flight update and its own cleanup task.
  • Several smaller ones: a leaking allocation in win::Data::clone(), a reserve() that should have been resize() in the logger, a null Monitor* dereference in MonitorsManager::start().

None of that changes how you use the library day to day, but it means the thing underneath is considerably more solid than it was in 2020 — particularly around startup/shutdown races and Windows error handling, which is exactly the kind of thing that only turns up under real-world load.

Using it

The public API hasn’t changed, so if you used an earlier version, this is a drop-in upgrade. If you’re new to it, here’s the gist.

Install it:

dotnet add package MyOddWeb.DirectoryWatcher

Watch a few paths and react when files show up:

using (var watch = new Watcher())
{
  watch.Add(new Request("c:\\", true));
  watch.Add(new Request("d:\\foo\\bar\\", true));

  watch.OnAddedAsync += async (f, t) =>
  {
    Console.WriteLine($"Added: {f.FullName}");
  };

  watch.Start();
}

Renames give you both the old and new name:

watch.OnRenamedAsync += async (f, t) =>
{
  Console.WriteLine($"{f.PreviousFullName} -> {f.FullName}");
};

And if you want to keep an eye on throughput, statistics are one line away:

watch.Add(new Request("c:\\", true, new Rates(50, 10000)));

watch.OnStatisticsAsync += async (s, t) =>
{
  Console.WriteLine($"{s.NumberOfEvents} events in the last {s.ElapsedTime}ms");
};

There’s a lot more in the README — logging levels, checking whether all your watch requests are actually ready, disposing cleanly, and so on.

What’s next

This is still Windows-only for now — the public interfaces are deliberately platform-agnostic, so a Linux or macOS backend is possible in principle, but it’s not on the immediate roadmap. For the moment, the goal was simply to bring the project back up to date and squash the bugs that had been sitting there since 2020.

If you use it and hit something, issues and PRs are very welcome — I’d rather it not take another six years for the next release.

Grab it from NuGet or GitHub.

What is a Feed-Forward Neural Network Layer? (With C++ & Python Examples)

Demystifying the Feed-Forward Neural Network Layer: A Hands-On Guide in C++ and Python

In the world of machine learning and artificial intelligence, neural networks are often treated as black boxes. But beneath the surface, they are built from simple, elegant mathematical blocks. The most fundamental and widely used of these blocks is the Feed-Forward Layer (also known as a Dense or Fully Connected layer).

Whether you are building a simple classifier or training complex deep learning models for financial market prediction, understanding the feed-forward layer is essential.

In this post, we will break down what a feed-forward layer is, how it works, and how to implement it. To make things practical, we will walk through a complete example (solving the classic XOR problem) using a lightweight, dependency-free C++ neural network library, and then compare it with implementations in Python, PyTorch, and TensorFlow.


What is a Feed-Forward Layer?

At its core, a feed-forward layer is a collection of artificial neurons where every input is connected to every output.

Inputs          Hidden Layer (FF)        Output Layer
 (X1)  --------->  (Neuron 1)  --------->  (Output Y)
        \        /          \        /
         \      /            \      /
          \    /              \    /
           \  /                \  /
 (X2)  -----\/-->  (Neuron 2)  -\/

When data passes through a feed-forward layer, three main operations occur:

  1. Weight Multiplication: Each input signal is multiplied by a “weight” representing the strength of the connection.
  2. Bias Addition: A “bias” value is added to the weighted sum. The bias allows the activation function to shift left or right, which is crucial for learning complex patterns.
  3. Activation Function: The combined sum is passed through a non-linear activation function (like Sigmoid, ReLU, or Tanh). This non-linearity allows the network to learn relationships that are more complex than a straight line.

Mathematically, for a given input vector $\mathbf{x}$, the output $\mathbf{y}$ of a feed-forward layer is represented as:

$$\mathbf{y} = f(\mathbf{W}\mathbf{x} + \mathbf{b})$$

Where:

  • $\mathbf{W}$ is the weight matrix.
  • $\mathbf{b}$ is the bias vector.
  • $f$ is the activation function.

The XOR Problem: Our Testing Ground

To demonstrate feed-forward layers in action, we will use the XOR (Exclusive OR) gate. The XOR gate is a classic problem in machine learning because it is linearly inseparable—you cannot separate the outputs ($0$ and $1$) with a single straight line.

To solve it, we need at least one hidden feed-forward layer to warp the input space so that it becomes separable.

Input 1 Input 2 Expected Output
0 0 0
0 1 1
1 0 1
1 1 0

1. The C++ Implementation (Using myoddweb::nn)

For performance-critical systems, trading bots, or resource-constrained environments, C++ is the language of choice. Below is an example using myoddweb::nn, a lightweight, dependency-free C++ neural network library.

Here, we configure a network with:

  • An Input Layer of 2 neurons.
  • One Hidden Feed-Forward Layer of 2 neurons (using Sigmoid activation).
  • An Output Layer of 1 neuron (using Sigmoid activation).
#include <iostream>
#include <vector>
#include "neuralnetwork/neuralnetwork.h"
#include "neuralnetwork/common/logger.h"

using namespace myoddweb::nn;

int main() 
{
  // 1. Define the network topology: 2 inputs, 2 hidden neurons, 1 output
  std::vector<unsigned> topology = { 2, 2, 1 };

  // 2. Define the hidden layer configurations (using Feed-Forward architecture)
  std::vector<LayerDetails> hidden_layers = {
    LayerDetails(
      Layer::Architecture::FF, 
      2, 
      activation(activation::method::sigmoid, 1.0), 
      0.0,                  // Dropout rate (0.0 = disabled)
      0.0,                  // Weight decay
      OptimiserType::SGD, 
      0.99                  // Momentum
    )
  };

  // 3. Define the output layer configuration
  auto output_layer = OutputLayerDetails(
    topology.back(), 
    activation(activation::method::sigmoid, 1.0), 
    ErrorCalculation::type::mse, 
    { 0.0, 0.0, 1.0, 0.0, false, 1.0 }, // Evaluation config
    0.0,                    // Weight decay
    OptimiserType::SGD, 
    0.99                    // Momentum
  );

  // 4. Build the configuration options
  auto options = NeuralNetworkOptions::create(topology)
    .with_batch_size(1)
    .with_output_layer_details(output_layer)
    .with_hidden_layers(hidden_layers)
    .with_learning_rate(0.1)
    .with_number_of_epoch(5000)
    .with_log_level(Logger::LogLevel::Info)
    .build();

  // 5. Create the neural network instance
  NeuralNetwork nn(options);

  // 6. Define training inputs (XOR inputs) and expected outputs
  std::vector<std::vector<double>> inputs = {
    { 0.0, 0.0 },
    { 0.0, 1.0 },
    { 1.0, 0.0 },
    { 1.0, 1.0 }
  };
  std::vector<std::vector<double>> outputs = {
    { 0.0 },
    { 1.0 },
    { 1.0 },
    { 0.0 }
  };

  // 7. Train the network
  std::cout << "Training the neural network...\n";
  nn.train(inputs, outputs);

  // 8. Run inference to check predictions
  std::cout << "\nInference Results:\n";
  for (const auto& input : inputs) 
  {
    auto result = nn.think(input);
    std::cout << "Input: {" << input[0] << ", " << input[1] 
              << "} -> Predicted: " << result[0] << "\n";
  }

  return 0;
}

What Output to Expect

As the network trains over 5000 epochs, the error (mean squared error) steadily decreases. By the end of the training, the output will look something like this:

Training the neural network...
Inference Results:
Input: {0, 0} -> Predicted: 0.0152
Input: {0, 1} -> Predicted: 0.9814
Input: {1, 0} -> Predicted: 0.9815
Input: {1, 1} -> Predicted: 0.0189

Notice how the outputs are extremely close to the expected XOR values ($0$ and $1$).


2. The Python Implementation (Using myoddweb::nn Bindings)

If you prefer Python but want to leverage the performance of the C++ engine, myoddweb::nn includes Python bindings. Here is the same example implemented in Python:

import neuralnetwork as nn

# 1. Define topology
topology = [2, 2, 1]

# 2. Configure hidden and output layers
hidden_activation = nn.Activation(nn.ActivationMethod.Sigmoid, 1.0)
hidden_layers = [
    nn.LayerDetails(
        nn.LayerArchitecture.FF, 
        2, 
        hidden_activation, 
        0.0, 0.0, 
        nn.OptimiserType.SGD, 
        0.99
    )
]

out_activation = nn.Activation(nn.ActivationMethod.Sigmoid, 1.0)
out_layer = nn.OutputLayerDetails(
    topology[-1], 
    out_activation, 
    nn.ErrorCalculationType.MSE,
    nn.EvaluationConfig(),
    0.0, 
    nn.OptimiserType.SGD, 
    0.99
)

# 3. Build neural network options
options = nn.NeuralNetworkOptions.create(topology) \
    .with_batch_size(1) \
    .with_hidden_layers(hidden_layers) \
    .with_output_layer_details(out_layer) \
    .with_learning_rate(0.1) \
    .with_number_of_epoch(5000) \
    .with_log_level(nn.LogLevel.Info) \
    .build()

# 4. Instantiate and train the network
net = nn.NeuralNetwork(options)

training_inputs = [
    [0.0, 0.0],
    [0.0, 1.0],
    [1.0, 0.0],
    [1.0, 1.0]
]
training_outputs = [
    [0.0],
    [1.0],
    [1.0],
    [0.0]
]

print("Training model...")
net.train(training_inputs, training_outputs)

# 5. Evaluate predictions
print("\nInference Results:")
for inputs, expected in zip(training_inputs, training_outputs):
    outputs = net.think(inputs)
    print(f"Input: {inputs} | Expected: {expected[0]} | Predicted: {outputs[0]:.4f}")

How Does This Compare to Other Libraries?

To see how the custom library design compares to major deep learning frameworks, let’s look at the same XOR problem built in PyTorch and TensorFlow.

Option A: PyTorch (Python)

PyTorch is favoured in research due to its pythonic and dynamic nature. In PyTorch, feed-forward layers are represented by the nn.Linear class.

import torch
import torch.nn as nn
import torch.optim as optim

# 1. Define model architecture using nn.Linear (Feed-Forward)
class XORModel(nn.Module):
    def __init__(self):
        super(XORModel, self).__init__()
        self.hidden = nn.Linear(2, 2)  # 2 inputs -> 2 hidden neurons
        self.sigmoid = nn.Sigmoid()
        self.output = nn.Linear(2, 1)  # 2 hidden neurons -> 1 output
        
    def forward(self, x):
        x = self.sigmoid(self.hidden(x))
        x = self.sigmoid(self.output(x))
        return x

model = XORModel()
criterion = nn.MSELoss()
optimizer = optim.SGD(model.parameters(), lr=0.1, momentum=0.99)

# 2. Data
inputs = torch.tensor([[0.0, 0.0], [0.0, 1.0], [1.0, 0.0], [1.0, 1.0]], dtype=torch.float32)
outputs = torch.tensor([[0.0], [1.0], [1.0], [0.0]], dtype=torch.float32)

# 3. Training
for epoch in range(5000):
    optimizer.zero_grad()
    preds = model(inputs)
    loss = criterion(preds, outputs)
    loss.backward()
    optimizer.step()

# 4. Inference
with torch.no_grad():
    predictions = model(inputs)
    print("PyTorch Results:")
    for inp, pred in zip(inputs, predictions):
        print(f"Input: {inp.tolist()} -> Predicted: {pred.item():.4f}")

Option B: TensorFlow / Keras (Python)

TensorFlow and Keras are widely used in enterprise production settings. Here, the feed-forward layer is represented by the Dense class.

import numpy as np
import tensorflow as tf
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense
from tensorflow.keras.optimizers import SGD

# 1. Define model architecture using Dense (Feed-Forward)
model = Sequential([
    Dense(2, input_dim=2, activation='sigmoid'), # Hidden layer
    Dense(1, activation='sigmoid')              # Output layer
])

# 2. Compile model
model.compile(
    optimizer=SGD(learning_rate=0.1, momentum=0.99), 
    loss='mean_squared_error'
)

# 3. Data
inputs = np.array([[0.0, 0.0], [0.0, 1.0], [1.0, 0.0], [1.0, 1.0]], dtype=np.float32)
outputs = np.array([[0.0], [1.0], [1.0], [0.0]], dtype=np.float32)

# 4. Training
model.fit(inputs, outputs, epochs=5000, batch_size=1, verbose=0)

# 5. Inference
predictions = model.predict(inputs)
print("TensorFlow/Keras Results:")
for inp, pred in zip(inputs, predictions):
    print(f"Input: {list(inp)} -> Predicted: {pred[0]:.4f}")

Conclusion & Next Steps

Feed-forward layers are the starting point of neural networks, mapping inputs to outputs via simple matrix multiplication and non-linear mappings. While high-level libraries like PyTorch and TensorFlow make it easy to assemble these layers, using a lightweight C++ library like myoddweb::nn on GitHub gives you deep control, portability, and zero-dependency integration.

If you are interested in looking under the hood of neural networks, learning how backpropagation is coded from scratch, or exploring recurrent layers like Elman RNNs and GRUs in native C++, check out the repository, star the project, and start experimenting!

Check out the project on GitHub: FFMG/neural-network

Updating from Ubuntu 22.04 to Ubuntu 23.04

Now that Ubuntu 23.04 has been released this is the steps to update from 22.04

Of course, this assumes that you are on 22.04.
This guide is very “brief”, if you are not sure what a command does, please look it up first and make sure you know what you are doing.

If you brick your release, don’t blame me, do your homework first 🙂

Update everything

Update everything and install the update managed, (you probably have it already)

sudo apt update && sudo apt upgrade -y && sudo apt install update-manager-core -y

You might be asked a couple of questions, just say yes

When this is done you need to update the manager to tell it that you can update to the next release version

Open the editor and change the upgrade policy

sudo nano /etc/update-manager/release-upgrades

Change the “prompt” to “normal”, (press Ctrl-0 to save and Ctrl-X to exit)

Change the source list to now look at the “lunar” instance instead of “jammy”

sudo sed -i ‘s/jammy/lunar/g’ /etc/apt/sources.list

Update everything … again

sudo apt update && sudo apt upgrade -y

Do the actual update

sudo apt dist-upgrade -y

You will be asked a few questions, it really depends on your environment and setup.

Most questions are fairly straight forward, so say yes to everything.

You will also be asked to remove/replace certain things.
Just read the messages carefully, most of them are fairly simple to understand.

Done

You are now done, so just reboot, (takes a bit longer).

sudo reboot

Once the reboot is done, you can check the version number.

How to add command line arguments to your piger commands

First the basics

The first argument is the command we wish to execute, the second one is the command line argument and the last one is if we wish to run as administrator or not, (with privileged access).

How to get the arguments?

You have various commands to get the arguments entered.

You can get the ones typed by the user

Or you can also get the selected folder if there is one

Remember that we could have selected more than one folder

Putting it all together

If you have an app like cmder or even the default command line app you can put it all together

And save the file and call it “cmder.lua” and save it in your root command folder, (or subdirectory).

Then if the user types

  • cmder home – they will go to their home directory
  • cmder – they will go to their system drive
  • cmder (with the cursor over a folder name) – they will go to that folder.

More?

You can get more information on the piger github page

Navigation