Friday, December 9, 2016

letItSnow() // javascript

// NOTE: This script does not work in some browsers, due to lack of support for SVG effects/animation.

'use strict';
function letItSnow(options) { // Joel Elliott <3 CC0 
    var SVG_NS = 'http://www.w3.org/2000/svg';

    var getSnowflakeArm = function (x, y, radius) {
        // TODO: this could be enhanced to make much more intersting snowflake shapes, with input parameters and/or randomized values
        // round all numbers to a max of 2 decimal places
        var x0 = Math.round(x * 100) / 100;
        var y0 = Math.round(y * 100) / 100;
        radius = Math.round(radius * 100) / 100;
        var radiusOffset1 = radius / 5;
        var xArm1 = radius / 5;
        var yArm1 = radius / 10;
        var radiusOffset2 = radiusOffset1 * 2;
        var xArm2 = xArm1 * 2;
        var yArm2 = yArm1 * 2;

        var armPoints = [];
        // draw from center to tip
        armPoints.push({ a: 'M', x: x0, y: y0 });
        armPoints.push({ a: 'L', x: x0, y: y0 - radius });

        // draw first spoke -- from tip, to center, to tip
        armPoints.push({ a: 'M', x: x0 + xArm1, y: y0 - radius + yArm1 });
        armPoints.push({ a: 'L', x: x0, y: y0 - radius + radiusOffset1 });
        armPoints.push({ a: 'L', x: x0 - xArm1, y: y0 - radius + yArm1 });

        // draw second spoke
        armPoints.push({ a: 'M', x: x0 + xArm2, y: y0 - radius + yArm2 });
        armPoints.push({ a: 'L', x: x0, y: y0 - radius + radiusOffset2 });
        armPoints.push({ a: 'L', x: x0 - xArm2, y: y0 - radius + yArm2 });

        var d = '';
        for (var j = 0; j < armPoints.length; j++) {
            var p = armPoints[j];
            d += '\n' + p.a + p.x + ',' + p.y;
        }
        return d;
    }

    var getSnowflake = function (x, y, radius, speed, spin, clockwise) {
        var secondsSpin = (11 - spin) / 4; // spin should be from 1 to 10
        var sign = clockwise ? 1 : -1;

        var g = document.createElementNS(SVG_NS, 'g');

        // the bigger the snowflake, the thicker you need the lines to be
        var strokeWidth = Math.round(radius / 15 * 100) / 100;
        var d = getSnowflakeArm(x, 0, radius);
        for (var i = 0; i < 6; i++) {
            var path = document.createElementNS(SVG_NS, 'path');
            //path.setAttribute('id','Mine' + i);
            path.setAttribute('d', d);
            path.setAttribute('fill', 'none');
            path.setAttribute('stroke-width', strokeWidth);
            path.setAttribute('stroke', '#ffffff');

            var animate = document.createElementNS(SVG_NS, 'animateTransform');
            animate.setAttribute('attributeName', 'transform');
            animate.setAttribute('type', 'rotate');
            animate.setAttribute('from', (i + 0) * 60 * sign + ' ' + x + ' ' + 0);
            animate.setAttribute('to', (i + 1) * 60 * sign + ' ' + x + ' ' + 0);
            animate.setAttribute('dur', secondsSpin + 's');
            animate.setAttribute('repeatCount', 'indefinite');
            path.appendChild(animate);

            g.appendChild(path);
        }

        var h = window.innerHeight;
        var secondsFall = h / speed;
        var fallenBeginSeconds = secondsFall * y / h;
        var animate = document.createElementNS(SVG_NS, 'animateTransform');
        animate.setAttribute('attributeName', 'transform');
        animate.setAttribute('type', 'translate');
        animate.setAttribute('from', '0 ' + (0 - radius));
        animate.setAttribute('to', '0 ' + (h + radius));
        animate.setAttribute('dur', secondsFall + 's');
        animate.setAttribute('begin', -fallenBeginSeconds + 's');
        animate.setAttribute('repeatCount', 'indefinite');
        g.appendChild(animate);

        return g;
    }

    { // validation/defaults for all used option values
        if (typeof options == 'undefined') options = {};
        var validOrDefault = function (value, min, max, defaultValue) {
            if (isNaN(value))
                return defaultValue;
            value = Number(value);
            if (value < min || value > max)
                return defaultValue;
            return value;
        }
        options.numFlakes = validOrDefault(options.numFlakes, 1, 500, 75);
        options.minSize = validOrDefault(options.minSize, 5, 50, 5);
        options.maxSize = validOrDefault(options.maxSize, 5, 50, 20);
        options.minSpin = validOrDefault(options.minSpin, 0, 10, 3);
        options.maxSpin = validOrDefault(options.maxSpin, 0, 10, 7);
        options.minSpeed = validOrDefault(options.minSpeed, 0, 100, 10);
        options.maxSpeed = validOrDefault(options.maxSpeed, 0, 100, 90);
        if (isNaN(options.zIndex)) options.zIndex = -1;
    }

    // create <svg>, set to size of page, add animated snowflakes
    var svg = document.createElementNS(SVG_NS, 'svg');
    svg.setAttribute('width', window.innerWidth);
    svg.setAttribute('height', window.innerHeight);
    svg.style.position = 'fixed';
    svg.style.left = 0;
    svg.style.top = 0;
    svg.style.zIndex = options.zIndex;

    for (var i = 0; i < options.numFlakes; i++) {
        var x = Math.random() * window.innerWidth;
        var y = Math.random() * window.innerHeight;
        var radius = Math.random() * (options.maxSize - options.minSize) + options.minSize;
        var spin = Math.random() * (options.maxSpin - options.minSpin) + options.minSpin;
        var clockwise = (Math.random() > 0.5);
        var speed = Math.random() * (options.maxSpeed - options.minSpeed) + options.minSpeed;
        var snowflake = getSnowflake(x, y, radius, speed, spin, clockwise);
        svg.appendChild(snowflake);
    }
    if (isNaN(options.delay) == false) {
        var delaySeconds = Number(options.delay);
        if (delaySeconds > 0) {
            svg.style.display = 'none';
            window.setTimeout(function () { svg.style.display = ''; },
             delaySeconds * 1000);
        }
    }
    document.body.appendChild(svg);
}

javascript - getRGBA()

/* getRGBA:
  Get the RGBA values of a color.
  If input is not a color, returns NULL, else returns an array of 4 values:
   red (0-255), green (0-255), blue (0-255), alpha (0-1)
*/
function getRGBA(value) {
  // get/create a 0 pixel element at the end of the document, to use to test properties against the client browser
  var e = document.getElementById('test_style_element');
  if (e == null) {
    e = document.createElement('span');
    e.id = 'test_style_element';
    e.style.width = 0;
    e.style.height = 0;
    e.style.borderWidth = 0;
    document.body.appendChild(e);
  }

  // use the browser to get the computed value of the input
  e.style.borderColor = '';
  e.style.borderColor = value;
  if (e.style.borderColor == '') return null;
  var computedStyle = window.getComputedStyle(e);
  var c
  if (typeof computedStyle.borderBottomColor != 'undefined') {
    // as always, MSIE has to make life difficult
    c = window.getComputedStyle(e).borderBottomColor;
  } else {
    c = window.getComputedStyle(e).borderColor;
  }
  var numbersAndCommas = c.replace(new RegExp('[^0-9.,]+','g'),'');
  var values = numbersAndCommas.split(',');
  for (var i = 0; i < values.length; i++)
    values[i] = Number(values[i]);
  if (values.length == 3) values.push(1);
  return values;
}

Saturday, August 27, 2016

Simple Creationary

Draw a card and build what you drew, using ten Legos or less, in one minute. Guess others' creations or have yours guessed to get points.
Details:
1) Everyone take ten Legos.
2) Everyone draw a card, and think of how to build a Lego creation that looks like that for a few seconds.
3) Start a timer for 60 seconds.
4) Everyone build at the same time. You may trade any block(s) with unused blocks in the center
5) Stop working when the time runs out.
6) Decide who will guess first, and go around once clockwise. Each player will choose a creation that they think is recognizable and make one guess. If the guess is correct, give one point to the guesser and one point to the builder.
7) First player(s) to 7 points wins!

Wednesday, May 11, 2016

SQL Server "Asynchronous" Stored Procedure (insert only)

So you have a SQL server stored procedure for logging, and its running a little slower than you like. There is no output, you just need to make sure the log message is put into the system. It would be really nice if there were a way to say "run this, and the client doesn't need to wait for a response", but unfortunately that's not a simple built-in feature. So how do you do it, with a minimum of headache?

Much of the credit for this goes to http://rusanu.com/2009/08/05/asynchronous-procedure-execution/. That post solves a slightly different problem - you want to execute a slow stored procedure that has no inputs, and check back later for the output -- without leaving a SQL connection open. But it was the basis of my solution here.

First, we setup a trivial example -- the destination table for the log message, and the stored procedure, which is slow but eventually does the insert. We will want to see what happens if the stored procedure fails, so we'll have a simple check to force an error.

CREATE TABLE [LogDestination]([LogValue] [varchar](max));
GO

CREATE PROCEDURE [usp_SlowProcedure]
  @message VARCHAR(MAX) AS
BEGIN
WAITFOR DELAY '0:00:00.5' -- wait 1/2 second
IF @message LIKE '%ERROR%'
  raiserror(@message, 16, 10);
ELSE
  INSERT INTO [LogDestination]([LogValue]) VALUES(@message);
END
GO

The solution will involve creating a QUEUE and a SERVICE, so you need to make sure your database has the Service Broker turned on. You can do that with this command:

ALTER DATABASE [MyDatabase] SET ENABLE_BROKER;

It sometimes seems to run forever -- it won't want to finish if there are any connections open on the database. You can force those to be closed with this option:

ALTER DATABASE [MyDatabase] SET ENABLE_BROKER WITH ROLLBACK IMMEDIATE;

Now to start creating the new objects. For reasons of circular dependencies, you have to CREATE one of these first, and then ALTER it later. That's just the breaks. Let's put down the fundamental parts:

CREATE PROCEDURE [usp_AsyncExecActivated] AS
  PRINT 'placeholder'
GO

CREATE QUEUE [AsyncExecQueue]
 WITH ACTIVATION(STATUS = ON
                ,PROCEDURE_NAME = [usp_AsyncExecActivated]
                ,MAX_QUEUE_READERS = 1
                ,EXECUTE AS OWNER)
GO

CREATE SERVICE [AsyncExecService] ON QUEUE [AsyncExecQueue] ([DEFAULT]);
GO

CREATE PROCEDURE [usp_SlowProcedureAsync]
   @message VARCHAR(MAX)
AS
BEGIN
SET NOCOUNT ON;
DECLARE @h UNIQUEIDENTIFIER;
BEGIN DIALOG CONVERSATION @h
        FROM SERVICE [AsyncExecService]
        TO SERVICE 'AsyncExecService'
        WITH ENCRYPTION = OFF;
SEND ON CONVERSATION @h (@message);
END CONVERSATION @h;
END
GO

Now we have:
  • [AsyncExecQueue] - the queue to hold the messages on
  • [AsyncExecService] - because only a service can write to a queue
  • [usp_AsyncExecActivated] - stored procedure that will run in the background whenever the queue is written to
  • [usp_SlowProcedureAsync] - to wrap all this up with, hiding the mess from the users. It has the same signature as [usp_SlowProcedure], but will return immediately.
One more consideration -- since this is completely asynchronous, the only errors the client can get are if the database is offline, out of space, etc. But if [usp_SlowProcedure] has an error, we want a table to just shove that input into, without leaving it on the queue. Or I guess you could omit this part, and the code that writes to it.

CREATE TABLE [AsyncErrored]([LogValue] [varbinary](max), [ErrorTime] DATETIME2 DEFAULT SYSDATETIME());
GO

Now we ALTER the background stored procedure, which is still just a placeholder, to actually do the processing.

ALTER PROCEDURE usp_AsyncExecActivated
AS
BEGIN

SET NOCOUNT ON;

DECLARE @h UNIQUEIDENTIFIER = NULL
      , @messageTypeName SYSNAME = NULL
      , @messageBody VARBINARY(MAX) = NULL;

RECEIVE TOP(1)
      @h = [conversation_handle]
    , @messageTypeName = [message_type_name]
    , @messageBody = [message_body]
FROM [AsyncExecQueue];

WHILE (@h IS NOT NULL)
  BEGIN

    BEGIN TRY
      DECLARE @message VARCHAR(MAX) = CAST(@messageBody AS VARCHAR(MAX));
      EXECUTE [usp_SlowProcedure] @MESSAGE
    END TRY
    BEGIN CATCH
      INSERT INTO [AsyncErrored]([LogValue]) VALUES(@messageBody);
    END CATCH
    END CONVERSATION @h WITH CLEANUP;
    SET @h = NULL;
    RECEIVE TOP(1)
          @h = [conversation_handle]
        , @messageTypeName = [message_type_name]
        , @messageBody = [message_body]
    FROM [AsyncExecQueue];

  END -- WHILE

END -- PROCEDURE usp_AsyncExecActivated

GO

That's all there is to it. But what good is a bunch of tables without running some tests to see it in action?

DECLARE @countQueue VARCHAR(10), @countDestination VARCHAR(10), @countErrored VARCHAR(10)

PRINT CAST(CAST(SYSDATETIME() AS TIME) AS VARCHAR(11)) + ' Test 1: invoke [usp_SlowProcedureAsync] one time with simple input.'
EXECUTE [usp_SlowProcedureAsync] 'Test input message';
SET @countQueue = (SELECT COUNT(1) FROM [AsyncExecQueue])
SET @countDestination = (SELECT COUNT(1) FROM [LogDestination])
SET @countErrored = (SELECT COUNT(1) FROM [AsyncErrored])
PRINT CAST(CAST(SYSDATETIME() AS TIME) AS VARCHAR(11)) + ' Queue=' + @countQueue + ' Destination=' + @countDestination + ' Errored=' + @countErrored
WHILE (@countQueue <> '0')
 BEGIN
  WAITFOR DELAY '0:00:00.2'
  SET @countQueue = (SELECT COUNT(1) FROM [AsyncExecQueue])
  SET @countDestination = (SELECT COUNT(1) FROM [LogDestination])
  SET @countErrored = (SELECT COUNT(1) FROM [AsyncErrored])
  PRINT CAST(CAST(SYSDATETIME() AS TIME) AS VARCHAR(11)) + ' Queue=' + @countQueue + ' Destination=' + @countDestination + ' Errored=' + @countErrored
 END

DELETE FROM [LogDestination];
DELETE FROM [AsyncErrored];
PRINT CAST(CAST(SYSDATETIME() AS TIME) AS VARCHAR(11)) + ' Test 2: invoke [usp_SlowProcedureAsync] five times, with fourth causing an error in [usp_SlowProcedure].'
EXECUTE [usp_SlowProcedureAsync] 'Test message 1';
EXECUTE [usp_SlowProcedureAsync] 'Test message 2';
EXECUTE [usp_SlowProcedureAsync] 'Test message 3';
EXECUTE [usp_SlowProcedureAsync] 'Test ERROR 4';
EXECUTE [usp_SlowProcedureAsync] 'Test message 5';
SET @countQueue = (SELECT COUNT(1) FROM [AsyncExecQueue])
SET @countDestination = (SELECT COUNT(1) FROM [LogDestination])
SET @countErrored = (SELECT COUNT(1) FROM [AsyncErrored])
PRINT CAST(CAST(SYSDATETIME() AS TIME) AS VARCHAR(11)) + ' Queue=' + @countQueue + ' Destination=' + @countDestination + ' Errored=' + @countErrored

WHILE (@countQueue <> '0')
 BEGIN
  WAITFOR DELAY '0:00:00.2'
  SET @countQueue = (SELECT COUNT(1) FROM [AsyncExecQueue])
  SET @countDestination = (SELECT COUNT(1) FROM [LogDestination])
  SET @countErrored = (SELECT COUNT(1) FROM [AsyncErrored])
  PRINT CAST(CAST(SYSDATETIME() AS TIME) AS VARCHAR(11)) + ' Queue=' + @countQueue + ' Destination=' + @countDestination + ' Errored=' + @countErrored

END

Here's some output I got from the above test run. You can see the 1/2 second pauses between each insert being processed.

15:59:16.62 Test 1: invoke [usp_SlowProcedureAsync] one time with simple input.
15:59:16.85 Queue=2 Destination=0 Errored=0
15:59:17.07 Queue=1 Destination=0 Errored=0
15:59:17.28 Queue=1 Destination=0 Errored=0
15:59:17.48 Queue=0 Destination=1 Errored=0

(1 row(s) affected)

(0 row(s) affected)
15:59:17.48 Test 2: invoke [usp_SlowProcedureAsync] five times, with fourth causing an error in [usp_SlowProcedure].
15:59:17.48 Queue=9 Destination=0 Errored=0
15:59:17.70 Queue=9 Destination=0 Errored=0
15:59:17.90 Queue=9 Destination=0 Errored=0
15:59:18.10 Queue=7 Destination=1 Errored=0
15:59:18.30 Queue=7 Destination=1 Errored=0
15:59:18.51 Queue=7 Destination=2 Errored=0
15:59:18.71 Queue=5 Destination=2 Errored=0
15:59:18.91 Queue=5 Destination=2 Errored=0
15:59:19.12 Queue=3 Destination=3 Errored=0
15:59:19.32 Queue=3 Destination=3 Errored=0
15:59:19.52 Queue=3 Destination=3 Errored=0
15:59:19.72 Queue=1 Destination=3 Errored=1
15:59:19.93 Queue=1 Destination=3 Errored=1

15:59:20.13 Queue=0 Destination=4 Errored=1

And if you run all of that SQL, and are done playing with it, here's the easy clean-up:

DROP TABLE [LogDestination]
DROP PROCEDURE [usp_SlowProcedure]
DROP TABLE [AsyncErrored]
DROP PROCEDURE [usp_AsyncExecActivated]
DROP SERVICE [AsyncExecService]
DROP PROCEDURE [usp_SlowProcedureAsync]

DROP QUEUE [AsyncExecQueue]

I hope someone eventually finds this useful; but if not, I will hopefully remember that I put this out here =-]