k

Streaks and highlights

It’s been a great year for space exploration. Now you get to pick the highlights.




k

Uncharted Supply First Aid Kit




k

Vollebak Eiderdown Puffer Jacket




k

Glenmorangie A Tale of Ice Cream Single Malt Scotch Whisky




k

YUKA 2000 Robot Lawn Mower




k

Sneak peek

Sneak peek — tray, citrus chess board, serving board.




k

Lake Town and Beorn

I've been messing around adding to the bear to days and trying not to rush myself. I'm so desperate to get some paint on it that I almost started today. However, I stopped myself and just added a bit more fur. This time I also ad buyded some to the bears face and now I'm finally happy with it.




Cheeky!
Some more help turns up to bolster Lake town and just in the nick of time.


Two of these bases are really shoddy militia types. Pitch forks are a great short hand for 'rabble'. I can't imagine they will hold for long against the goblin horde.

Another hero base .

I think he looks angry, ragged and damn right mean now...let's get him painted up.


I've added hints of blue so the whole force will tie together.


 The men start to suffer from the fierce wolf packs.


The men with their long swords and the tough veterans from the Iron hills.




  • lord Of The Rings
  • lord Of The Rings.

k

Dark Age Irish Warband


 This is me working out my warband for a Dark age campaign. I can't really get going on it until I have finished my Hobbit armies for an up coming show. So In the meantime time this sketch of the warband will have to do. Out of the hat I got the Black shield Irish.

The rules will be One hour wargames, and it's interesting to see the small size of the warband. Then again it is a skirmish game.




k

Dwarf King in the shield wall

A Dwarf king and his elite guard take to the field.

 Amongst the Eagle helms of the shield wall is the kings champion himself. Nothing must get to the king and the champions blue axe, will see that nothing does.

I could use this base for King Balin when he tries to retake Moria. A future project of mine.
The King points out floors in the enemies formation to the chief engineer.
The Dragon standard flies over head echoing the Kings mighty dragon helm.
This base has been created for the upcoming Hobbit battle, replacing Dain's base in the ranks. This allows Dain to roam freely as a character on the field.

 The figures are beautiful old sculpts from Asgard and Citadel. There has been a bit of tweaking on a couple of them, like the axe and the standard.



  • lord Of The Rings
  • lord Of The Rings.

k

Dark age Irish

I have to say after the mad month of painting for the Partizan show, my painting mojo is well and truly fried. So I thought I would ease back into things with some gentle building and sculpting.

These figures are a One Hour Wargames Rules campaign set in the age of Arthur. I was picked for the Black shield Irish and so bought a couple of plastic boxes.
 

Skirmishers.

I thought a few Celtic blankets and cloaks wouldn't go a miss. The kits come with some cloaks, but I thought I'd have a go at making my own.
The leader and hero figure from Crusader miniatures. The Druid was a purchase from eBay.
These are a mixture of plastic kits from Gripping Beast and Wargames Atlantic. It's always nice to have more heads etc to create variety in the units.




k

Dark Age Irish

Here is a small warband of Dark age Irishmen.  They are meant to be the Black shield Irish from the Winter King books. I struggled with just painting black shields as I wanted to paint some designs. I got around this by painting some fancy shields, then painting them black then rubbing off the paint.

This represents the warband painting over their war shields with black. After some heavy campaigning and harsh weather, not to mention dips in the sea, some of the black paint has started to wear off.

There's enough Celtic design underneath to show through.
Not many more to do now, just the druid and four skirmishers.
These are a mix of Crusader, Gripping beast plastic and Wargames Atlantic plastics. They have been very enjoyable to paint too.

 
The new rock pools are a perfect setting for Raiding Black shields.




k

Finished Rock pools


The last stage was to gloss varnish the water in the pools. This really brought them to life and gave them a sparkle.

The kelp and some of the green rocks were also given a lick with the gloss brush too. Not too much, just enough to give them a damp look.






The bottom of the pools were painted with loads of people's of different colours. This was then given a green wash, then a brown one to add depth to the pool.

 




k

Eugene Zaikonnikov: Breaking the Kernighan's Law

"Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it.." — Brian W. Kernighan.

I'm a sucker for sage advice much as anyone else, and Kernighan is certainly right on money in the epigraph. Alas there comes a time in programmer's career when you just end up there despite the warning. It could be that you were indeed too clever for your own good, or maybe the code isn't quite yours anymore after each of your colleague's take on it over the years. Or just sometimes, the problem is indeed so hard that it strains your capacity as a coder.

It would usually start with a reasonable idea made into first iteration code. The solution looks fundamentally sound but then as you explore the problem space further it begins to seep nuance, either as manifestation of some real world complexity or your lack of foresight. When I run into this my first instinct is to instrument the code. If the problem is formidable you got to respect it: flailing around blindly modifying things or ugh, doing a rewrite at this stage is almost guaranteed to be a waste of time. It helps to find a promising spot, chisel it, gain a foothold in the problem, and repeat until you crack it. Comfortable debugging tools here can really help to erode the original Kernighan coefficient from 2 to maybe 1.6 or 1.4 where you can still have a chance.

Lisp users are fortunate with the options of interactive debugging, and one facility I reach often for is the plain BREAK. It's easy enough to wrap it into a conditional for particular matches you want to debug. However sometimes you want it to trigger after a particular sequence of events across different positions in code has taken place. While still doable it quickly becomes cumbersome and this state machine starts to occupy too much mental space which is already scarce. So one day, partly as a displacement activity from being intimidated by a Really Hard Problem I wrote down my debugging patterns as a handful of macros.

Enter BRAKE. Its features reflect my personal preferences so are not necessarily your cup of tea but it could be a starting point to explore in this direction. Things it can do:

  • act as a simple BREAK with no arguments (duh)
  • wrap an s-expression, passing through its values upon continuing
  • trigger sequentially based on the specified position for a common tag
  • allow for marks that don't trigger the break but mark the position as reached
  • provide conditional versions for the expressions above
  • print traces of tagged breakpoints/marks

If you compile functions with debug on you hopefully should be able to see the wrapped sexpr's result values.

(use-package '(brake))

(defun fizzbuzz ()
  (loop for n from 100 downto 0
	for fizz = (zerop (mod n 3))
	for buzz = (zerop (mod n 5)) do
	(format t "~a "
		(if (not (or fizz buzz))
		    (format nil "~d" n)
		  (brake-when (= n 0)
			      (concatenate 'string
					   (if fizz "Fizz" "")
					   (if buzz "Buzz" "")))))))

These macros try to detect common cases for tagged sequences being either aborted via break or completed to the last step, resetting them after to the initial state. However it is possible for a sequence to end up "abandoned", which can be cleaned up by a manual command.

Say in the example below we want to break when the two first branches were triggered in a specific order. The sequence of 1, 3, 4 will reinitialize once the state 4 is reached, allowing to trigger continuously. At the same time if we blow our stack it should reset to initial when aborting.

(defun ack (m n)
  (cond ((zerop m) (mark :ack 3 (1+ n)))
        ((zerop n) (mark :ack 1 (ack (1- m) 1)))
        (t (brake :ack 4 (ack (1- m) (ack m (1- n)))))))

In addition there are a few utility functions to report on the state of brakepoints, enable or disable brakes based on tags and turn tracing on or off. Tracing isn't meant to replace the semantics of TRACE but to provide a souped up version of debug by print statements everyone loves.

CL-USER> (report-brakes)
Tag :M is DISABLED, traced, with 3 defined steps, current state is initial
Tag :F is DISABLED with 2 defined steps, current state is 0
Tag :ACK is ENABLED with 3 defined steps, current state is initial

Disabling breakpoints without recompilation is really handy and something I find using all the time. The ability to wrap a sexpr was often sorely missed when using BREAK in constructs without implicit body.

Sequencing across threads is sketchy as the code isn't guarded but in many cases it can work, and the appeal of it in debugging races is clear. One of those days I hope to make it more robust while avoiding potential deadlocks but it isn't there yet. Where it already shines tho is in debugging complex iterations, mutually recursive functions and state machines.




k

Patrick Stein: Ray Tracing In One Weekend (in Lisp, and n-dimenions)

Earlier this year, I started working through the online book Ray Tracing In One Weekend (Book 1). I have been following along with it in Common Lisp, and I have been extending it all from 3-dimensional to n-dimensional.

I reproduced 4-dimensional versions of all of the book images which you can see on my weekend-raytracer github page.

Here is the final image. This is a 250-samples-per-pixel, 640x360x10 image plane of three large hyperspheres (one mirrored, one diffuse, one glass) atop a very large, diffuse hypersphere. Also atop this very large hypersphere are a bunch of smaller hyperspheres of varying colors and materials. The image is rendered with some defocus-blur.

Final image of 4-dimensional scene

Caveat: This depends on a patched version of the policy-cond library that is not in the current Quicklisp distribution but should be in the next.




k

Quicklisp news: October 2024 Quicklisp dist update now available

 New projects: 

  • adp-github — ADP extension to generate github markdown files. — MIT
  • adp-plain — Add Documentation, Please... using plain text. An extension of ADP to generate files with barely additional features. — MIT
  • allioli — Alliolification — MIT
  • alternate-asdf-system-connections — Allows for ASDF system to be connected so that auto-loading may occur. This is a fork of asdf-system-connections and incorporates a load-system-driven mechanism for loading dependencies and also loads the dependencies of the connections. — MIT
  • cbor — CBOR encoder/decoder — MIT
  • charje.documentation — Documentation is an opinionated yet customizable docstring parsing library. — AGPL V3 or any later version
  • chipi — House automation bus in Common Lisp — Apache-2
  • cl-aseprite — Aseprite file format parser — GPLv3
  • cl-astar — A heavily optimized yet flexible A* pathfinding algorithm implementation — MIT
  • cl-ceigen-lite — A Common Lisp wrapper around CEIGEN-LITE - which is itself a C wrapper around the C++ Eigen library. — MIT
  • cl-cf — Computations using continued fractions — GPL-3
  • cl-concord — CONCORD implementation based on Common Lisp — LGPL
  • cl-duckdb — CFFI wrapper around the DuckDB C API — MIT License
  • cl-fastcgi — FastCGI wrapper for Common Lisp — BSD License
  • cl-flx — Rewrite emacs-flx in Common Lisp — MIT
  • cl-frugal-uuid — Common Lisp UUID library with zero dependencies — MIT License
  • cl-gog-galaxy — A wrapper for the GOG Galaxy SDK — zlib
  • cl-lc — List comprehensions — MIT
  • cl-naive-ptrees — Functions to make it easier to work with plist(s) and plist trees. Works with plist(s) pairs as units and not as individual list items. — MIT
  • cl-qoa — An implementation of the Quite Okay Audio format. — zlib
  • cl-reddit — Reddit client api library — BSD
  • cl-resvg — An up-to-date bindings library for the resvg SVG rendering library — zlib
  • cl-trivial-clock — Common Lisp library to get accurate wall-clock times on multiple platforms — MIT License
  • clack-cors — A Clack middleware to set CORS related HTTP headers. — Unlicense
  • clack-prometheus — Clack middleware to serve stats in Prometheus format. — Unlicense
  • clith — Common Lisp wITH macro. A general WITH macro. — MIT
  • clj-arrows — Implements Clojure-styled threading/transformation macros. — MIT
  • clos-encounters — A collection of OOP patterns benefiting from the CLOS MOP. — Unlicense
  • coalton — An efficient, statically typed functional programming language that supercharges Common Lisp. — MIT
  • cocoas — A toolkit library to help deal with CoreFoundation, Cocoa, and objc — zlib
  • com.danielkeogh.graph — A fast an reliable graph library. — MIT
  • fast-mpsc-queue — Multi-Producer Single-Consumer queue implementation. — MIT
  • file-finder — File finder. Enable rapid file search, inspection and manipulation. — GPL3+
  • golden-utils — A utility library. — MIT
  • hiccl — HTML generator for Common Lisp — MIT
  • hsx — Hypertext S-expression — MIT
  • hunchentoot-stuck-connection-monitor — Monitors hunchentoot connections and logs the connections stuck in the same state for a long time (due to slow or inactive clients and network stream timeouts that hunchentoot tries to utilize not working properly). Offers an option to shutdown the stuck connections sockets manually or automatically, thus unblocking the connection threads and preventing thread and socket leak. See https://github.com/edicl/hunchentoot/issues/189 — BSD-2-Clause
  • incless — A portable and extensible Common Lisp printer implementation (core) — BSD
  • inravina — A portable and extensible Common Lisp pretty printer. — MIT
  • invistra — A portable and extensible Common Lisp FORMAT implementation — BSD
  • knx-conn — KNXnet/IP implementation in Common Lisp — GNU GPL, version 3
  • machine-state — Retrieve machine state information about CPU time, memory usage, etc. — zlib
  • myweb — simple web server written in common lisp for educational reasons — LGPLv3
  • noisy — Perlin noise for arbitrary numbers of dimensions. — MIT
  • nontrivial-gray-streams — A compatibility layer for Gray streams including extensions — MIT
  • open-with — Open a file in a suitable external program — zlib
  • openai-openapi-client — Openai API client — AGPLv3+
  • openrpc — CI for Common Lisp OpenRPC library. — BSD
  • parse-number-range — Parses LOOP's convenient "for-as-arithmetic" syntax into 5 simple values: from, to, limit-kind (:inclusive, :exclusive or nil if unbounded), by (step) and direction (+ or -)). Further related utilities are provided. Intended for easy implementation of analogous functionality in other constructs. — Public Domain
  • precise-time — Precise time measurements — zlib
  • pregexp — Portable regular expressions for Common Lisp — MIT-like
  • progressons — Display a progress bar on one line. — MIT
  • quaviver — A portable and extensible floating point string library — MIT
  • quilc — A CLI front-end for the Quil compiler — Apache License 2.0 (See LICENSE.txt)
  • qvm — An implementation of the Quantum Abstract Machine. — Apache License 2.0 (See LICENSE.txt)
  • random-sampling — Functions to generate random samples with various distributions — zlib
  • rs-dlx — Knuth's Algorithm X with dancing links. — Modified BSD License
  • scrapycl — The web scraping framework for writing crawlers in Common Lisp. — Unlicense
  • smoothers — Statistical methods to create approximating functions that attempt to capture important patterns in the data, while leaving out noise or other fine-scale structures/rapid phenomena. — MS-PL
  • trivial-adjust-simple-array — A tiny utility to change array size ensuring it is simple. — MIT
  • trivial-system-loader — A system installation/loading abstraction for Common Lisp — MIT
  • trivial-toplevel-commands — Trivial Toplevel Commands allows to define toplevel commands available on most implementations in a portable fashion. — BSD-3 Clause
  • trivial-toplevel-prompt — Portability library to customize REPL prompts. — BSD-3 Clause
  • utf8-input-stream — A UTF-8 string input stream over a binary stream for Common Lisp — MIT
  • whereiseveryone.command-line-args — Automatically create a command-line-argument parser for a given Common Lisp function definition. — AGPL v3 or any later version

Updated projects: 3b-bmfont, 3bgl-shader, 3bmd, 3d-math, 3d-spaces, 40ants-asdf-system, 40ants-slynk, access, acclimation, action-list, adhoc, adopt, adp, agnostic-lizard, alexandria, alexandria-plus, anatevka, anypool, april, arc-compat, architecture.builder-protocol, array-utils, arrow-macros, assoc-utils, async-process, atomics, auto-restart, aws-sdk-lisp, babel, bdef, bike, binary-structures, binding-arrows, birch, blackbird, bordeaux-threads, calm, carrier, caveman, ccldoc, cephes.cl, cepl, cerberus, cffi, cffi-object, cffi-ops, chanl, chunga, ci, ci-utils, ciao, cl-6502, cl-algebraic-data-type, cl-all, cl-ansi-term, cl-async, cl-atelier, cl-autowrap, cl-base32, cl-bmas, cl-bmp, cl-bnf, cl-brewer, cl-buchberger, cl-cmark, cl-collider, cl-colors2, cl-confidence, cl-containers, cl-cookie, cl-csv, cl-custom-hash-table, cl-cxx-jit, cl-data-structures, cl-dbi, cl-digraph, cl-dot, cl-enchant, cl-environments, cl-fast-ecs, cl-fbx, cl-fluent-logger, cl-form-types, cl-forms, cl-freetype2, cl-gamepad, cl-github-v3, cl-gltf, cl-gobject-introspection, cl-graph, cl-grip, cl-gserver, cl-hamcrest, cl-hash-util, cl-html-readme, cl-i18n, cl-info, cl-ini, cl-ipfs-api2, cl-kanren, cl-lib-helper, cl-liballegro, cl-liballegro-nuklear, cl-log, cl-markless, cl-marshal, cl-migratum, cl-mixed, cl-modio, cl-mount-info, cl-mpg123, cl-mssql, cl-mustache, cl-mysql, cl-neovim, cl-netpbm, cl-oju, cl-opengl, cl-opensearch-query-builder, cl-opus, cl-patterns, cl-plus-ssl-osx-fix, cl-ppcre, cl-project, cl-protobufs, cl-pslib, cl-pslib-barcode, cl-rashell, cl-readline, cl-sat.minisat, cl-sdl2-image, cl-sdl2-mixer, cl-sdl2-ttf, cl-sendgrid, cl-sentry-client, cl-skkserv, cl-smtp, cl-ssh-keys, cl-steamworks, cl-str, cl-svg, cl-telegram-bot, cl-threadpool, cl-tiled, cl-torrents, cl-tqdm, cl-transducers, cl-transit, cl-unicode, cl-unification, cl-unix-sockets, cl-utils, cl-vectors, cl-vorbis, cl-wavefront, cl-webdriver-client, cl-webkit, cl-webmachine, cl-who, clack, clack-pretend, clad, classimp, clast, clath, clavier, clazy, clerk, clgplot, climacs, clingon, clip, clj-con, clj-re, clobber, clog, clog-ace, clog-collection, clog-plotly, clog-terminal, clohost, closer-mop, clss, cluffer, clunit2, clx, cmd, codata-recommended-values, codex, coleslaw, collectors, colored, com-on, common-lisp-jupyter, commondoc-markdown, compiler-macro-notes, conduit-packages, consfigurator, contextl, croatoan, ctype, cytoscape-clj, damn-fast-priority-queue, dartscluuid, data-frame, data-lens, datafly, dbus, decompress, defenum, definer, definitions, deflate, defmain, deploy, depot, deptree, dexador, dissect, djula, dns-client, doc, docs-builder, dsm, dufy, easter-gauss, easy-audio, easy-macros, easy-routes, eclector, equals, erjoalgo-webutil, erudite, esrap, event-emitter, external-program, external-symbol-not-found, fare-csv, fare-scripts, fast-http, fast-websocket, file-attributes, file-notify, file-select, filesystem-utils, fiveam, fiveam-matchers, flexi-streams, float-features, flow, fn, fset, functional-trees, fuzzy-dates, gadgets, generic-cl, github-api-cl, glfw, glsl-toolkit, harmony, hashtrie, helambdap, http2, hunchentoot, imago, in-nomine, inferior-shell, introspect-environment, ironclad, jose, js, json-mop, jsonrpc, jzon, khazern, lack, lass, lemmy-api, letv, lichat-protocol, lichat-tcp-client, linear-programming, lisp-binary, lisp-chat, lisp-critic, lisp-pay, lisp-stat, lispcord, lla, local-time, log4cl-extras, logging, lru-cache, magicl, maiden, maidenhead, manifolds, math, mcclim, memory-regions, messagebox, method-combination-utilities, mgl-pax, misc-extensions, mito, mk-defsystem, mmap, mnas-package, mnas-string, moira, multiposter, mutility, mutils, named-closure, ndebug, neural-classifier, new-op, nibbles, nibbles-streams, ningle, nodgui, north, numerical-utilities, nytpu.lisp-utils, omglib, ook, open-location-code, openapi-generator, orizuru-orm, overlord, papyrus, parachute, parse-number, pathname-utils, petalisp, phos, picl, plot, plump, plump-sexp, pngload, policy-cond, polymorphic-functions, postmodern, ppath, prometheus-gc, psychiq, purgatory, py4cl, py4cl2, py4cl2-cffi, qlot, qoi, query-fs, quick-patch, quickhull, quri, random-state, reblocks, reblocks-auth, reblocks-file-server, reblocks-lass, reblocks-navigation-widget, reblocks-parenscript, reblocks-prometheus, reblocks-typeahead, reblocks-ui, reblocks-websocket, rove, s-dot2, sandalphon.lambda-list, sb-fastcgi, sc-extensions, sel, select, serapeum, shasht, shop3, si-kanren, sketch, slime, slite, sly, snooze, spinneret, staple, static-vectors, statistics, stepster, stmx, stripe, swank-crew, swank-protocol, sxql, symath, system-locale, taglib, teddy, ten, testiere, tfeb-lisp-hax, tfm, tiny-routes, tooter, trivia, trivial-arguments, trivial-clipboard, trivial-file-size, trivial-gray-streams, trivial-main-thread, trivial-octet-streams, trivial-package-locks, trivial-package-manager, trivial-sanitize, trivial-shell, type-templates, typo, uax-15, uiop, usocket, vellum, vellum-binary, vellum-csv, vellum-postmodern, verbose, vernacular, vom, websocket-driver, winhttp, with-branching, with-contexts, woo, xhtmlambda, xml-emitter, yason, zippy, zpb-ttf.

Removed projects: abstract-arrays, ahungry-fleece, cl-cheshire-cat, cl-darksky, cl-epoch, cl-naive-store, convolution-kernel, dense-arrays, extensible-compound-types, extensible-optimizing-coerce, fast-generic-functions, flac-metadata, freebsd-ffi, listoflist, luckless, one-more-re-nightmare, postmodern-localtime, stumpwm-dynamic-float, stumpwm-sndioctl, unicly.

To get this update, use:

 (ql:update-dist "quicklisp")

Sorry this update took so long. My goal is to resume monthly releases.

Enjoy!




k

TurtleWare: Dynamic Vars - The Empire Strikes Back

Table of Contents

  1. Thread Local storage exhausted
  2. The layer of indirection
  3. I can fix her
  4. Let's write some tests!
  5. Summary

Thread Local storage exhausted

In the last post I've described a technique to use dynamic variables by value instead of the name by utilizing the operator PROGV. Apparently it works fine on all Common Lisp implementations I've tried except from SBCL, where the number of thread local variables is by default limited to something below 4000. To add salt to the injury, these variables are not garbage collected.

Try the following code to crash into LDB:

(defun foo ()
  (loop for i from 0 below 4096 do
    (when (zerop (mod i 100))
      (print i))
    (progv (list (gensym)) (list 42)
      (values))))
(foo)

This renders our new technique not very practical given SBCL popularity. We need to either abandon the idea or come up with a workaround.

The layer of indirection

Luckily for us we've already introduced a layer of indirection. Operators to access dynamic variables are called DLET, DSET and DREF. This means, that it is enough to provide a kludge implementation for SBCL with minimal changes to the remaining code.

The old code works the same as previously except that instead of SYMBOL-VALUE we use the accessor DYNAMIC-VARIABLE-VALUE, and the old call to PROGV is now DYNAMIC-VARIABLE-PROGV. Moreover DYNAMIC-EFFECTIVE-SLOT used functions BOUNDP and MAKUNBOUND, so we replace these with DYNAMIC-VARIABLE-BOUND-P and DYNAMIC-VARIABLE-MAKUNBOUND. To abstract away things further we also introduce the constructor MAKE-DYNAMIC-VARIABLE

(defpackage "EU.TURTLEWARE.BLOG/DLET"
  (:local-nicknames ("MOP" #+closer-mop "C2MOP"
                           #+(and (not closer-mop) ecl) "MOP"
                           #+(and (not closer-mop) ccl) "CCL"
                           #+(and (not closer-mop) sbcl) "SB-MOP"))
  (:use "CL"))
(in-package "EU.TURTLEWARE.BLOG/DLET")

(eval-when (:compile-toplevel :execute :load-toplevel)
  (unless (member :bordeaux-threads *features*)
    (error "Please load BORDEAUX-THREADS."))
  (when (member :sbcl *features*)
    (unless (member :fake-progv-kludge *features*)
      (format t "~&;; Using FAKE-PROGV-KLUDGE for SBCL.~%")
      (push :fake-progv-kludge *features*))))

(defmacro dlet (bindings &body body)
  (flet ((pred (binding)
           (and (listp binding) (= 2 (length binding)))))
    (unless (every #'pred bindings)
      (error "DLET: bindings must be lists of two values.~%~
                Invalid bindings:~%~{ ~s~%~}" (remove-if #'pred bindings))))
  (loop for (var val) in bindings
        collect var into vars
        collect val into vals
        finally (return `(dynamic-variable-progv (list ,@vars) (list ,@vals)
                           ,@body))))

(defmacro dset (&rest pairs)
  `(setf ,@(loop for (var val) on pairs by #'cddr
                 collect `(dref ,var)
                 collect val)))

(defmacro dref (variable)
  `(dynamic-variable-value ,variable))

;;; ...

(defmethod mop:slot-boundp-using-class
    ((class standard-class)
     object
     (slotd dynamic-effective-slot))
  (dynamic-variable-bound-p (slot-dvar object slotd)))

(defmethod mop:slot-makunbound-using-class
    ((class standard-class)
     object
     (slotd dynamic-effective-slot))
  (dynamic-variable-makunbound (slot-dvar object slotd)))

With these in place we can change the portable implementation to conform.

#-fake-progv-kludge
(progn
  (defun make-dynamic-variable ()
    (gensym))

  (defun dynamic-variable-value (variable)
    (symbol-value variable))

  (defun (setf dynamic-variable-value) (value variable)
    (setf (symbol-value variable) value))

  (defun dynamic-variable-bound-p (variable)
    (boundp variable))

  (defun dynamic-variable-makunbound (variable)
    (makunbound variable))

  (defmacro dynamic-variable-progv (vars vals &body body)
    `(progv ,vars ,vals ,@body)))

I can fix her

The implementation for SBCL will mediate access to the dynamic variable value with a synchronized hash table with weak keys. The current process is the key of the hash table and the list of bindings is the value of the hash table. For compatibility between implementations the top level value of the symbol will be shared.

The variable +FAKE-UNBOUND+ is the marker that signifies, that the variable has no value. When the list of bindings is EQ to +CELL-UNBOUND+, then it means that we should use the global value. We add new bindings by pushing to it.

#+fake-progv-kludge
(progn
  (defvar +fake-unbound+ 'unbound)
  (defvar +cell-unbound+ '(no-binding))

  (defclass dynamic-variable ()
    ((tls-table
      :initform (make-hash-table :synchronized t :weakness :key)
      :reader dynamic-variable-tls-table)
     (top-value
      :initform +fake-unbound+
      :accessor dynamic-variable-top-value)))

  (defun make-dynamic-variable ()
    (make-instance 'dynamic-variable))

  (defun dynamic-variable-bindings (dvar)
    (let ((process (bt:current-thread))
          (tls-table (dynamic-variable-tls-table dvar)))
      (gethash process tls-table +cell-unbound+)))

  (defun (setf dynamic-variable-bindings) (value dvar)
    (let ((process (bt:current-thread))
          (tls-table (dynamic-variable-tls-table dvar)))
      (setf (gethash process tls-table +cell-unbound+) value))))

We define two readers for the variable value - one that simply reads the value, and the other that signals an error if the variable is unbound. Writer for its value either replaces the current binding, or if the value cell is unbound, then we modify the top-level symbol value. We use the value +FAKE-UNBOUND+ to check whether the variable is bound and to make it unbound.

#+fake-progv-kludge
(progn
  (defun %dynamic-variable-value (dvar)
    (let ((tls-binds (dynamic-variable-bindings dvar)))
      (if (eq tls-binds +cell-unbound+)
          (dynamic-variable-top-value dvar)
          (car tls-binds))))

  (defun dynamic-variable-value (dvar)
    (let ((tls-value (%dynamic-variable-value dvar)))
      (when (eq tls-value +fake-unbound+)
        (error 'unbound-variable :name "(unnamed)"))
      tls-value))

  (defun (setf dynamic-variable-value) (value dvar)
    (let ((tls-binds (dynamic-variable-bindings dvar)))
      (if (eq tls-binds +cell-unbound+)
          (setf (dynamic-variable-top-value dvar) value)
          (setf (car tls-binds) value))))

  (defun dynamic-variable-bound-p (dvar)
    (not (eq +fake-unbound+ (%dynamic-variable-value dvar))))

  (defun dynamic-variable-makunbound (dvar)
    (setf (dynamic-variable-value dvar) +fake-unbound+)))

Finally we define the operator to dynamically bind variables that behaves similar to PROGV. Note that we PUSH and POP from the thread-local hash table DYNAMIC-VARIABLE-BINDINGS, so no synchronization is necessary.

#+fake-progv-kludge
(defmacro dynamic-variable-progv (vars vals &body body)
  (let ((svars (gensym))
        (svals (gensym))
        (var (gensym))
        (val (gensym)))
    `(let ((,svars ,vars))
       (loop for ,svals = ,vals then (rest ,svals)
             for ,var in ,svars
             for ,val = (if ,svals (car ,svals) +fake-unbound+)
             do (push ,val (dynamic-variable-bindings ,var)))
       (unwind-protect (progn ,@body)
         (loop for ,var in ,svars
               do (pop (dynamic-variable-bindings ,var)))))))

Let's write some tests!

But of course, we are going to also write a test framework. It's short, I promise. As a bonus point the API is compatibile with fiveam, so it is possible to drop tests as is in the appropriate test suite.

(defvar *all-tests* '())

(defun run-tests ()
  (dolist (test (reverse *all-tests*))
    (format *debug-io* "Test ~a... " test)
    (handler-case (funcall test)
      (serious-condition (c)
        (format *debug-io* "Failed: ~a~%" c))
      (:no-error (&rest args)
        (declare (ignore args))
        (format *debug-io* "Passed.~%")))))

(defmacro test (name &body body)
  `(progn
     (pushnew ',name *all-tests*)
     (defun ,name () ,@body)))

(defmacro is (form)
  `(assert ,form))

(defmacro pass ())

(defmacro signals (condition form)
  `(is (block nil
         (handler-case ,form
           (,condition () (return t)))
         nil)))

(defmacro finishes (form)
  `(is (handler-case ,form
         (serious-condition (c)
           (declare (ignore c))
           nil)
         (:no-error (&rest args)
           (declare (ignore args))
           t))))

Now let's get to tests. First we'll test our metaclass:

(defclass dynamic-let.test-class ()
  ((slot1 :initarg :slot1 :dynamic nil :accessor slot1)
   (slot2 :initarg :slot2 :dynamic t   :accessor slot2)
   (slot3 :initarg :slot3              :accessor slot3))
  (:metaclass class-with-dynamic-slots))

(defparameter *dynamic-let.test-instance-1*
  (make-instance 'dynamic-let.test-class
                 :slot1 :a :slot2 :b :slot3 :c))

(defparameter *dynamic-let.test-instance-2*
  (make-instance 'dynamic-let.test-class
                 :slot1 :x :slot2 :y :slot3 :z))

(test dynamic-let.1
  (let ((o1 *dynamic-let.test-instance-1*)
        (o2 *dynamic-let.test-instance-2*))
    (with-slots (slot1 slot2 slot3) o1
      (is (eq :a slot1))
      (is (eq :b slot2))
      (is (eq :c slot3)))
    (with-slots (slot1 slot2 slot3) o2
      (is (eq :x slot1))
      (is (eq :y slot2))
      (is (eq :z slot3)))))

(test dynamic-let.2
  (let ((o1 *dynamic-let.test-instance-1*)
        (o2 *dynamic-let.test-instance-2*))
    (signals error (slot-dlet (((o1 'slot1) 1)) nil))
    (slot-dlet (((o1 'slot2) :k))
      (is (eq :k (slot-value o1 'slot2)))
      (is (eq :y (slot-value o2 'slot2))))))

(test dynamic-let.3
  (let ((o1 *dynamic-let.test-instance-1*)
        (exit nil)
        (fail nil))
    (flet ((make-runner (values)
             (lambda ()
               (slot-dlet (((o1 'slot2) :start))
                 (let ((value (slot2 o1)))
                   (unless (eq value :start)
                     (setf fail value)))
                 (loop until (eq exit t) do
                   (setf (slot2 o1) (elt values (random (length values))))
                   (let ((value (slot2 o1)))
                     (unless (member value values)
                       (setf fail value)
                       (setf exit t))))))))
      (let ((r1 (bt:make-thread (make-runner '(:k1 :k2))))
            (r2 (bt:make-thread (make-runner '(:k3 :k4))))
            (r3 (bt:make-thread (make-runner '(:k5 :k6)))))
        (sleep .1)
        (setf exit t)
        (map nil #'bt:join-thread (list r1 r2 r3))
        (is (eq (slot2 o1) :b))
        (is (null fail))))))

Then let's test the dynamic variable itself:

(test dynamic-let.4
  "Test basic dvar operators."
  (let ((dvar (make-dynamic-variable)))
    (is (eql 42 (dset dvar 42)))
    (is (eql 42 (dref dvar)))
    (ignore-errors
     (dlet ((dvar :x))
       (is (eql :x (dref dvar)))
       (error "foo")))
    (is (eql 42 (dref dvar)))))

(test dynamic-let.5
  "Test bound-p operator."
  (let ((dvar (make-dynamic-variable)))
    (is (not (dynamic-variable-bound-p dvar)))
    (dset dvar 15)
    (is (dynamic-variable-bound-p dvar))
    (dynamic-variable-makunbound dvar)
    (is (not (dynamic-variable-bound-p dvar)))))

(test dynamic-let.6
  "Test makunbound operator."
  (let ((dvar (make-dynamic-variable)))
    (dset dvar t)
    (is (dynamic-variable-bound-p dvar))
    (finishes (dynamic-variable-makunbound dvar))
    (is (not (dynamic-variable-bound-p dvar)))))

(test dynamic-let.7
  "Test locally bound-p operator."
  (let ((dvar (make-dynamic-variable)))
    (is (not (dynamic-variable-bound-p dvar)))
    (dlet ((dvar 15))
      (is (dynamic-variable-bound-p dvar)))
    (is (not (dynamic-variable-bound-p dvar)))))

(test dynamic-let.8
  "Test locally unbound-p operator."
  (let ((dvar (make-dynamic-variable)))
    (dset dvar t)
    (is (dynamic-variable-bound-p dvar))
    (dlet ((dvar nil))
      (is (dynamic-variable-bound-p dvar))
      (finishes (dynamic-variable-makunbound dvar))
      (is (not (dynamic-variable-bound-p dvar))))
    (is (dynamic-variable-bound-p dvar))))

(test dynamic-let.9
  "Stress test the implementation (see :FAKE-PROGV-KLUDGE)."
  (finishes                              ; at the same time
    (let ((dvars (loop repeat 4096 collect (make-dynamic-variable))))
      ;; ensure tls variable
      (loop for v in dvars do
        (dlet ((v 1))))
      (loop for i from 0 below 4096
            for r = (random 4096)
            for v1 in dvars
            for v2 = (elt dvars r) do
              (when (zerop (mod i 64))
                (pass))
              (dlet ((v1 42)
                     (v2 43))
                (values))))))

(test dynamic-let.0
  "Stress test the implementation (see :FAKE-PROGV-KLUDGE)."
  (finishes                             ; can be gc-ed
    (loop for i from 0 below 4096 do
      (when (zerop (mod i 64))
        (pass))
      (dlet (((make-dynamic-variable) 42))
        (values)))))

All that is left is to test both dynamic variable implementations:

BLOG/DLET> (lisp-implementation-type)
"ECL"
BLOG/DLET> (run-tests)
Test DYNAMIC-LET.1... Passed.
Test DYNAMIC-LET.2... Passed.
Test DYNAMIC-LET.3... Passed.
Test DYNAMIC-LET.4... Passed.
Test DYNAMIC-LET.5... Passed.
Test DYNAMIC-LET.6... Passed.
Test DYNAMIC-LET.7... Passed.
Test DYNAMIC-LET.8... Passed.
Test DYNAMIC-LET.9... Passed.
Test DYNAMIC-LET.0... Passed.
NIL

And with the kludge:

BLOG/DLET> (lisp-implementation-type)
"SBCL"
BLOG/DLET> (run-tests)
Test DYNAMIC-LET.1... Passed.
Test DYNAMIC-LET.2... Passed.
Test DYNAMIC-LET.3... Passed.
Test DYNAMIC-LET.4... Passed.
Test DYNAMIC-LET.5... Passed.
Test DYNAMIC-LET.6... Passed.
Test DYNAMIC-LET.7... Passed.
Test DYNAMIC-LET.8... Passed.
Test DYNAMIC-LET.9... Passed.
Test DYNAMIC-LET.0... Passed.
NIL

Summary

In this post we've made our implementation to work on SBCL even when there are more than a few thousand dynamic variables. We've also added a simple test suite that checks the basic behavior.

As it often happens, after achieving some goal we get greedy and achieve more. That's the case here as well. In the next (and the last) post in this series I'll explore the idea of adding truly thread-local variables without a shared global value. This will be useful for lazily creating context on threads that are outside of our control. We'll also generalize the implementation so it is possible to subclass and implement ones own flavor of a dynamic variable.




k

One Week E-Book Sale of Vacuum Flowers!!! One Full Week!!!

.

 


Open Road Media, which publishes several of my e-books, has announced a one-week reduction in price of Vacuum Flowers. Starting this October 18 and running through October 25, 2024, it will be available for $1.99. That's in the US only.

So if you're an e-book reader and have been curious about my novel... well, there you are.


And if you don't already know . . .

Vacuum Flowers is what used to be called a Grand Tour of the Solar System. Rebel Elizabeth Mudlark is operating off of stolen wetware and on the run from very dangerous people. She arrives in the inner system on a cometary orbit, which takes her through a great variety of human and post-human societies, including the most dangerous one of all--Earth.

That bit about the cometary orbit is not incidental. Comets enter the Inner System on either a hyperbolic or a parabolic orbit. The one is open, the other closed. I knew that the book would end with Rebel Elizabeth Mudlark standing in the stardocks with a coffin at her feet. But I didn't know if the person within the coffin would be alive or dead or if REM would someday return to the Inner System or was leaving it forever. I only decided that when I came to write the last page.


*




k

My Halloween Season Story, "Unquiet Graves," in CLARKESWORLD

 .


 

I am always happiest when a story of mine comes into print. Today, I have the joy of introducing you to "Unquiet Graves," a seasonal tale of graveyard misbehavior and betrayal. Oh, and there's nothing supernatural about it at all.

You can read the story here. But if you're like me, you'll just go to Clarkesworld, look over the table of contents, and decide which story you want to read first. Mine by preference, but follow your whim.

 

And for those who like trivia . . .

I came up with the handheld's app many long years ago and it took forever to come up with a story for it. You'll notice that it is left unnamed in the story. That's because its secret name was "The Graveyard Reader." Which is the title of a well-known story by Theodore Sturgeon.  While I was writing the story, I thought of it as "The New Graveyard Reader." But Sturgeon's story and mine go off in totally different directions, and giving mine (or even the app) a title suggesting there was some implicit connection between the two would only cause confusion.

The title I finally came up with was derived from "The Unquiet Grave" by that most prolific of all poets, Anonymous. If you look it up, I suggest you do so after reading my story. It gives away some of the plot.


*

 




k

One-Day E-Book Sale of Vacuum Flowers

 .



Once again, one of my e-books will briefly be on sale! Vacuum Flowers will be available in the US for only $1.99. Here's the news from Open Road Media:

Hello,

We are pleased to let you know that the following ebook(s) will be featured in price promotions soon.

ISBN13TitleAuthorPromo TypeCountryStart DateEnd DatePromo Price
9781504036504Vacuum FlowersSwanwick, MichaelORM - Portalist NLUS2024-11-132024-11-13$1.99



Open Road will promote the feature via social media. We hope you can share the deal with your network as well. You can subscribe to the newsletters at the links below so that you will get the direct link to the deal on the day that it appears.

NewsletterLink
  Early Bird Books    Subscribe Now  
The LineupSubscribe Now
The PortalistSubscribe Now
Murder & MayhemSubscribe Now
A Love So TrueSubscribe Now
The ArchiveSubscribe Now
The ReaderSubscribe Now



Please let us know if you have any questions. We are thrilled to be part of this promotion; hope you are too!

Best,
The Open Road Editorial Team


And because you've probably wondered . . .

I've been asked this many times, but the answer is no: I don't have a nude drawing of Gardner Dozois hanging in my living room. It's in the upstairs hallway. Anyway, he's wearing a sheet, so much of him is covered.

Robert Walters posed Gardner as the evil genius Jonaman for one of the illos (back when SF magazines had illustrations) that went with the serialization of Vacuum Flowers in Asimov's, way back when.

It's not the sightliest picture. But it is treasured.


*










k

Why Virat Kohli, Jasprit Bumrah were missing from Perth nets; India ramp up privacy amid Manchester United-like security - Hindustan Times

  1. Why Virat Kohli, Jasprit Bumrah were missing from Perth nets; India ramp up privacy amid Manchester United-like security  Hindustan Times
  2. Virat Kohli in focus: Intense net session begins for upcoming Test series against Australia  The Times of India
  3. Virat Kohli in Australia for BGT: A timeline  India Today
  4. Black veil of secrecy: India begin training in privacy in Perth  ESPNcricinfo
  5. India to play intra-squad warm-up match at WACA on Friday ahead of Australia Tests but BCCI denies public viewing  Hindustan Times





k

3 Children, 3 Women Missing After 10 Suspected Kuki Militants Killed In Encounter In Manipur's Jiribam - NDTV

  1. 3 Children, 3 Women Missing After 10 Suspected Kuki Militants Killed In Encounter In Manipur's Jiribam  NDTV
  2. Manipur on boil: 2 more bodies found, 6 missing  The Times of India
  3. Additional paramilitary forces rushed to Manipur amid spike in ethnic violence  Hindustan Times
  4. Letters to The Editor — November 13, 2024  The Hindu
  5. 2 men found dead, 6 of family missing day after militants killed in Manipur  India Today




k

hey man like water

Today on Married To The Sea: hey man like water


This RSS feed is brought to you by Drew and Natalie's podcast Garbage Brain University. Our new series Everything Is Real explores the world of cryptids, aliens, quantum physics, the occult, and more. If you use this RSS feed, please consider supporting us by becoming a patron. Patronage includes membership to our private Discord server and other bonus material non-patrons never see!




k

hey i think

Today on Married To The Sea: hey i think


This RSS feed is brought to you by Drew and Natalie's podcast Garbage Brain University. Our new series Everything Is Real explores the world of cryptids, aliens, quantum physics, the occult, and more. If you use this RSS feed, please consider supporting us by becoming a patron. Patronage includes membership to our private Discord server and other bonus material non-patrons never see!




k

fuck god dammit

Today on Married To The Sea: fuck god dammit


This RSS feed is brought to you by Drew and Natalie's podcast Garbage Brain University. Our new series Everything Is Real explores the world of cryptids, aliens, quantum physics, the occult, and more. If you use this RSS feed, please consider supporting us by becoming a patron. Patronage includes membership to our private Discord server and other bonus material non-patrons never see!




k

knock knock buy house

Today on Married To The Sea: knock knock buy house


This RSS feed is brought to you by Drew and Natalie's podcast Garbage Brain University. Our new series Everything Is Real explores the world of cryptids, aliens, quantum physics, the occult, and more. If you use this RSS feed, please consider supporting us by becoming a patron. Patronage includes membership to our private Discord server and other bonus material non-patrons never see!




k

drink brother

Today on Married To The Sea: drink brother


This RSS feed is brought to you by Drew and Natalie's podcast Garbage Brain University. Our new series Everything Is Real explores the world of cryptids, aliens, quantum physics, the occult, and more. If you use this RSS feed, please consider supporting us by becoming a patron. Patronage includes membership to our private Discord server and other bonus material non-patrons never see!




k

check will bounce

Today on Married To The Sea: check will bounce


This RSS feed is brought to you by Drew and Natalie's podcast Garbage Brain University. Our new series Everything Is Real explores the world of cryptids, aliens, quantum physics, the occult, and more. If you use this RSS feed, please consider supporting us by becoming a patron. Patronage includes membership to our private Discord server and other bonus material non-patrons never see!




k

take these pills

Today on Married To The Sea: take these pills


This RSS feed is brought to you by Drew and Natalie's podcast Garbage Brain University. Our new series Everything Is Real explores the world of cryptids, aliens, quantum physics, the occult, and more. If you use this RSS feed, please consider supporting us by becoming a patron. Patronage includes membership to our private Discord server and other bonus material non-patrons never see!




k

did you even think

Today on Married To The Sea: did you even think


This RSS feed is brought to you by Drew and Natalie's podcast Garbage Brain University. Our new series Everything Is Real explores the world of cryptids, aliens, quantum physics, the occult, and more. If you use this RSS feed, please consider supporting us by becoming a patron. Patronage includes membership to our private Discord server and other bonus material non-patrons never see!




k

dave and buster fun tokens

Today on Married To The Sea: dave and buster fun tokens


This RSS feed is brought to you by Drew and Natalie's podcast Garbage Brain University. Our new series Everything Is Real explores the world of cryptids, aliens, quantum physics, the occult, and more. If you use this RSS feed, please consider supporting us by becoming a patron. Patronage includes membership to our private Discord server and other bonus material non-patrons never see!




k

im allowed to speak

Today on Married To The Sea: im allowed to speak


This RSS feed is brought to you by Drew and Natalie's podcast Garbage Brain University. Our new series Everything Is Real explores the world of cryptids, aliens, quantum physics, the occult, and more. If you use this RSS feed, please consider supporting us by becoming a patron. Patronage includes membership to our private Discord server and other bonus material non-patrons never see!




k

does this suck

Today on Married To The Sea: does this suck


This RSS feed is brought to you by Drew and Natalie's podcast Garbage Brain University. Our new series Everything Is Real explores the world of cryptids, aliens, quantum physics, the occult, and more. If you use this RSS feed, please consider supporting us by becoming a patron. Patronage includes membership to our private Discord server and other bonus material non-patrons never see!




k

just shut the fuck up

Today on Married To The Sea: just shut the fuck up


This RSS feed is brought to you by Drew and Natalie's podcast Garbage Brain University. Our new series Everything Is Real explores the world of cryptids, aliens, quantum physics, the occult, and more. If you use this RSS feed, please consider supporting us by becoming a patron. Patronage includes membership to our private Discord server and other bonus material non-patrons never see!




k

snake arm

Today on Married To The Sea: snake arm


This RSS feed is brought to you by Drew and Natalie's podcast Garbage Brain University. Our new series Everything Is Real explores the world of cryptids, aliens, quantum physics, the occult, and more. If you use this RSS feed, please consider supporting us by becoming a patron. Patronage includes membership to our private Discord server and other bonus material non-patrons never see!




k

duck carrying around

Today on Married To The Sea: duck carrying around


This RSS feed is brought to you by Drew and Natalie's podcast Garbage Brain University. Our new series Everything Is Real explores the world of cryptids, aliens, quantum physics, the occult, and more. If you use this RSS feed, please consider supporting us by becoming a patron. Patronage includes membership to our private Discord server and other bonus material non-patrons never see!




k

wheed smoke

Today on Married To The Sea: wheed smoke


This RSS feed is brought to you by Drew and Natalie's podcast Garbage Brain University. Our new series Everything Is Real explores the world of cryptids, aliens, quantum physics, the occult, and more. If you use this RSS feed, please consider supporting us by becoming a patron. Patronage includes membership to our private Discord server and other bonus material non-patrons never see!




k

old growth grow back

Today on Married To The Sea: old growth grow back


This RSS feed is brought to you by Drew and Natalie's podcast Garbage Brain University. Our new series Everything Is Real explores the world of cryptids, aliens, quantum physics, the occult, and more. If you use this RSS feed, please consider supporting us by becoming a patron. Patronage includes membership to our private Discord server and other bonus material non-patrons never see!




k

i know what youre thinkin

Today on Married To The Sea: i know what youre thinkin


This RSS feed is brought to you by Drew and Natalie's podcast Garbage Brain University. Our new series Everything Is Real explores the world of cryptids, aliens, quantum physics, the occult, and more. If you use this RSS feed, please consider supporting us by becoming a patron. Patronage includes membership to our private Discord server and other bonus material non-patrons never see!




k

okay joseph

Today on Married To The Sea: okay joseph


This RSS feed is brought to you by Drew and Natalie's podcast Garbage Brain University. Our new series Everything Is Real explores the world of cryptids, aliens, quantum physics, the occult, and more. If you use this RSS feed, please consider supporting us by becoming a patron. Patronage includes membership to our private Discord server and other bonus material non-patrons never see!




k

blue check meetup

Today on Married To The Sea: blue check meetup


This RSS feed is brought to you by Drew and Natalie's podcast Garbage Brain University. Our new series Everything Is Real explores the world of cryptids, aliens, quantum physics, the occult, and more. If you use this RSS feed, please consider supporting us by becoming a patron. Patronage includes membership to our private Discord server and other bonus material non-patrons never see!




k

stop freaking out

Today on Married To The Sea: stop freaking out


This RSS feed is brought to you by Drew and Natalie's podcast Garbage Brain University. Our new series Everything Is Real explores the world of cryptids, aliens, quantum physics, the occult, and more. If you use this RSS feed, please consider supporting us by becoming a patron. Patronage includes membership to our private Discord server and other bonus material non-patrons never see!




k

machine that makes fucks

Today on Married To The Sea: machine that makes fucks


This RSS feed is brought to you by Drew and Natalie's podcast Garbage Brain University. Our new series Everything Is Real explores the world of cryptids, aliens, quantum physics, the occult, and more. If you use this RSS feed, please consider supporting us by becoming a patron. Patronage includes membership to our private Discord server and other bonus material non-patrons never see!




k

our stock price

Today on Married To The Sea: our stock price


This RSS feed is brought to you by Drew and Natalie's podcast Garbage Brain University. Our new series Everything Is Real explores the world of cryptids, aliens, quantum physics, the occult, and more. If you use this RSS feed, please consider supporting us by becoming a patron. Patronage includes membership to our private Discord server and other bonus material non-patrons never see!