Handling generic parameters in .Net dynamic code generation

2017-02-14 17:26:55 Coding C++

In C#, it is possible to write code that generate dynamic assemblies, classes and methods at run time. This is a very powerful metaprogramming trick as it allows developers to generate IL instructions according to runtime situations. It also helps to reduce the size of an assembly by creating classes and methods only when needed. I heard that this approach is used to create XmlSerializer instances for better performance.

This article is not an introduction of how to do the magic (though it is fairly simple and you can learn from the skeleton code below without any difficulity). It explores a very specific situation when generic typing meets dynamic IL emitting.

(more…)


Draw curves in WPF efficiently

2016-12-16 11:19:38 Coding C++ WPF

I have a customized canvas inheriting Canvas and overrides the OnRender(DrawingContext dc) method. And I need to draw some curves. I have precomputed the points on the curve and stored them in a List<Tuple<double, double>> because I am drawing different parts of the curve at different time. So, it is natural to come up with

for (int i = start; i <= end; ++i)
{
    dc.DrawLine(CurvePen, new Point(list[i].Item1, list[i].Item2), new Point(list[i+1].Item1, list[i+1].Item2));
}

However this approach is slow and CPU-intensive as hell. Guess what? I had a naive test and wrote

var gstr = new StringBuilder();
gstr.Append($"M {list[start].Item1},{list[start].Item2} L ");
for (int i = start + 1; i <= end; ++i)
{
    gstr.Append($"{t.Item1},{t.Item2} ");
}
dc.DrawGeometry(null, CurvePen, Geometry.Parse(gstr.ToString()));

Basically the code constructs a Path Markup, parse it to a Geometry object and draw it. It looks terrible – that’s a lot of string operations… but it is actually very, very fast. In fact, much faster than the multiple DrawLine calls.

One possible optimization is to, apparently, precompute all the Geometry objects, although it may result in worse performance because that’s a lot of objects. So I’ll just stick to this construct-on-the-fly thing at the moment 😛


Behind the scene of Camera.ScreenToWorldpoint() in Unity 2D

2016-11-19 09:00:12 Coding C++ Unity

For some reason I need to compute Camera.main.ScreenToWorldpoint manually. So, I need to know how it works internally. Since in 2D we have $$P_s = (P_v.x * w, P_v.y * h)$$, it is enough to understand how ViewportToWorldpoint works.

In theory (CG basics): $$P_v = M_{proj} * M_{view} * P_w$$, thus

$$P_w = M_{view}^{-1} * M_{proj}^{-1} * P_v $$

However, in Unity, $$P_v$$ is in the range [0, 1] rather than [-1, 1], which means it is actually

$$P_v = M_{extra} * M_{proj} * M_{view} * P_w$$

and thus

$$P_w = M_{view}^{-1} * M_{proj}^{-1} * M_{extra}^{-1} * P_v $$

Where

M_extra = [1, 0, 0, 1]   inv(M_extra) = [1, 0, 0, -0.5]
          [0, 1, 0, 1]                  [0, 1, 0, -0.5]
          [0, 0, 1, 0]                  [0, 0, 1,  0  ]
          [0, 0, 0, 2]                  [0, 0, 0,  0.5]

Note that we don’t care about Z since it is in 2D orthographic projection mode.

Now, in the language of Unity:

Initialize:

var magicMatrix = new Matrix4x4
{
    m00 = 1,
    m03 = -0.5f,
    m11 = 1,
    m13 = -0.5f,
    m22 = 1,
    m33 = 0.5f
};
_originalViewportToWorldMatrix = Camera.main.cameraToWorldMatrix*Camera.main.projectionMatrix.inverse*magicMatrix;

Compute:

private Vector3 OriginalScreenToWorld(float x, float y, float setZ)
{
    var vec = _originalViewportToWorldMatrix*new Vector4(x/_width, y/_height, 0, 1);
    return new Vector3(vec.x/vec.w, vec.y/vec.w, setZ);
}

Record all cout output in C++

2016-03-27 17:41:32 Coding C++

我去年做UROP的时候,用C++写了一个控制台程序。后来希望把整个过程自动化,也就需要导出程序的所有输出。然而这些输出本身是在cout里的,我也不想弄得太复杂(比如再写个程序wrap在外面什么的)。同时我希望这个过程是透明的——我不需要修改那些用了cout<<的地方。

于是先写一个用于把输入一个streambuf的字符分配到两个streambuf中的dist_streambuf:

/*
dist_streambuf.h
Author: logchan
Created: 2015-07-29
Header file for class dist_streambuf.
A dist_streambuf object is used to distribute the output operations to two streambuf objects (primary and secondary).
It can be used in some tricks, such as recording cout.
*/
#ifndef _LOGCHAN_DIST_STREAMBUF_H
#define _LOGCHAN_DIST_STREAMBUF_H
#include <streambuf>
#include <locale>
#include <ios>

using std::streambuf;
using std::streamsize;
using std::streampos;
using std::streamoff;
using std::locale;
using std::ios_base;

class dist_streambuf : public streambuf
{
public:
    dist_streambuf(streambuf* primary, streambuf* secondary);
    streambuf* primary_buf();
    streambuf* primary_buf(streambuf* newbuf);
    streambuf* secondary_buf();
    streambuf* secondary_buf(streambuf* newbuf);

protected:
    // Locales
    void imbue(const locale& loc);
    // Buffer management and positioning
    streambuf* setbuf(char* s, streamsize n);
    streampos seekoff(streamoff off, ios_base::seekdir way, ios_base::openmode which = ios_base::in | ios_base::out);
    streampos seekpos(streampos sp, ios_base::openmode which = ios_base::in | ios_base::out);
    int sync();
    // Input functions
    // Not implemented. It does not make sense to "distribute input to" two streambufs.
    // Output functions
    streamsize xsputn(const char* s, streamsize n);
    int overflow(int c = EOF);

private:
    streambuf* buf1;
    streambuf* buf2;
};

#endif //_LOGCHAN_DIST_STREAMBUF_H

以及实现的cpp文件:

#include "dist_streambuf.h"
#include <streambuf>
#include <stdexcept>
using namespace std;

dist_streambuf::dist_streambuf(streambuf * primary, streambuf * secondary)
{
    if (primary == nullptr)
    {
        throw invalid_argument("null primary buf is not allowed.");
    }

    buf1 = primary;
    buf2 = secondary;
}

streambuf * dist_streambuf::primary_buf()
{
    return buf1;
}

streambuf * dist_streambuf::primary_buf(streambuf * newbuf)
{
    if (newbuf == nullptr)
    {
        throw invalid_argument("null primary buf is not allowed.");
    }

    streambuf* prevbuf = buf1;
    buf1 = newbuf;
    return prevbuf;
}

streambuf * dist_streambuf::secondary_buf()
{
    return buf2;
}

streambuf * dist_streambuf::secondary_buf(streambuf * newbuf)
{
    streambuf* prevbuf = buf2;
    buf2 = newbuf;
    return prevbuf;
}
streamsize dist_streambuf::xsputn(const char * s, streamsize n)
{
    streamsize r = buf1->sputn(s, n);
    if (buf2 != nullptr)
    {
        buf2->sputn(s, n);
    }

    return r;
}

int dist_streambuf::overflow(int c)
{
    int r = buf1->sputc(c);
    if (buf2 != nullptr)
    {
        buf2->sputc(c);
    }

    return r;
}

void dist_streambuf::imbue(const locale & loc)
{
    buf1->pubimbue(loc);
    if (buf2 != nullptr)
    {
        buf2->pubimbue(loc);
    }
}

streambuf * dist_streambuf::setbuf(char * s, streamsize n)
{
    streambuf* r = buf1->pubsetbuf(s, n);
    if (buf2 != nullptr)
    {
        buf2->pubsetbuf(s, n);
    }

    return r;
}

streampos dist_streambuf::seekoff(streamoff off, ios_base::seekdir way, ios_base::openmode which)
{
    streampos r = buf1->pubseekoff(off, way, which);
    if (buf2 != nullptr)
    {
        buf2->pubseekoff(off, way, which);
    }

    return r;
}

streampos dist_streambuf::seekpos(streampos sp, ios_base::openmode which)
{
    streampos r = buf1->pubseekpos(sp, which);
    if (buf2 != nullptr)
    {
        buf2->pubseekpos(sp, which);
    }

    return r;
}

int dist_streambuf::sync()
{
    int r = buf1->pubsync();
    if (buf2 != nullptr)
    {
        buf2->pubsync();
    }

    return r;
}

dist_streambuf允许buf2为nullptr。使用时,通过cout本身的buffer和一个sstream的buffer构造一个dist_streambuf并用其替换cout原来的buffer就可以了:

// initialize the logging of cout
auto preserved_cout_buf = cout.rdbuf();
auto logstream_buf = logstream.rdbuf();
dist_buf = new dist_streambuf(preserved_cout_buf, logstream_buf);
cout.rdbuf(dist_buf);

如果希望临时关闭分配,既可以把preserved_cout_buf还给cout,也可以用dist_buf->secondary_buf(nullptr);来使logstream_buf不再接受到新的字符。当然,如果想分配到更多的buffer,也可以把一个dist_streambuf作为另一个的primary或者secondary……这么一说,构造器没有检查传入的是不是等于this似乎有点危险……失策

啊,多么好用的东西。