Category Archives: Life at a glance

DotNet Framework 源码阅读记录

Dotnet Core最近发布了2.1版本,相信也更加趋于稳定了。 虽然我接触.NET是从3.5+开始,不过这次可以从1.0开始见证它的发展了。 最近闲的时候阅读了一部分源码,虽然之前也看过不少,不过没有记录。 现在有了博客,就记录一点。

主要看的是下面几个仓库里面的内容:

Void 是个结构体(Struct)

Public struct Void{}

AggregateException

AggregateException 是伴随着async/await引入的新的类型, 有个Flatten方法, 可以将内部异常展开(貌似是BFS算法). 在调用时,将内部异常保存在一个ReadOnlyCollection中,不可改变. –> See how stack trace generated

DBNull

DbNull.Value is an instance of DbNull type. ToString() => string.Empty
Inheriated from Iconvertible, all other convertation will throw exception. InvalidCastException

DateTime

使用大量的缓存
* Days per 100 years/400years
* 里面存储了dates to 1601/1899/1970/10000,因为对应了不同的纪元 -> https://en.wikipedia.org/wiki/Epoch_(reference_date)
* Ticks per ms/s/Minute/Hour/Day
* s_daysToMonth365/s_daysToMonth366

字典

Dictionay – hash with chaining
HashTable – hash using open addressing

Overflow检测

在数字一部分, 有一段很精妙的overflow检测方式, 在做算法题的时候会用到。

        public static int Abs(int value)
        {
            if (value < 0)
            {
                value = -value;
                if (value < 0)
                {
                    ThrowAbsOverflow();
                }
            }
            return value;
        }

以及几个有意思的常量, 对于自己设计框架有帮助。

        public const double NegativeInfinity = (double)-1.0 / (double)(0.0);
        public const double PositiveInfinity = (double)1.0 / (double)(0.0);
        public const double NaN = (double)0.0 / (double)0.0;

Span

Span<T> https://msdn.microsoft.com/en-us/magazine/mt814808.aspx

DebuggerTypeProxy

在设计框架里面的类型时, 可以使用DebuggerTypeProxy在debugger里面显示的内容。

炉石传说卡组代码生成机制

炉石传说套牌代码分析

炉石传说可以共享套牌了,复制一串代码在炉石里面就可以直接导入,有了系统的支持,导入套牌就方便多了。那么这串字符是如何生成的呢? 经过一些分析和学习,记录如下。

我在HearthSim这个网站上找到了相关的分析和代码,https://hearthsim.info/docs/deckstrings/ 。这个网站提供了几个编程语言的生成代码,以C#版本的为例做分析。

代码

仓库地址: https://github.com/HearthSim/HearthDb。拖下来编译时,它会再去获取另外一个炉石数据库仓库: https://github.com/HearthSim/hsdata

有了这2个仓库后, 代码就可以正常运行了。在接触陌生的代码时,了解其功能除了读文档外,就是阅读测试代码了。
PS:里面有俩项目Build会失败,组件引用冲突错乱,无视即可。只需Build HearthDB

通过阅读测试方法TestDeckStrings,可以对这段字符有个大概了解。

[TestMethod]
public void TestDeckStrings()
{
    var deck = DeckSerializer.Deserialize(DeckString);
    Assert.AreEqual(CardIds.Collectible.Warrior.GarroshHellscream, deck.GetHero().Id);
    var cards = deck.GetCards();
    Assert.AreEqual(30, cards.Values.Sum());
    var heroicStroke = cards.FirstOrDefault(c => c.Key.Id == CardIds.Collectible.Warrior.HeroicStrike);
    Assert.IsNotNull(heroicStroke);
    Assert.AreEqual(2, heroicStroke.Value);
}

可以看到字符解析为byte数组,然后对其顺序遍历解析,具体分析见下图。

AAECAQcCrwSRvAIOHLACkQP/A44FqAXUBaQG7gbnB+8HgrACiLACub8CAA==

byte[43] {
0,                +--------->   placeholder
1,                |--------->   version always 1
2,                |--------->   FormatType
1,                |--------->   Num Heroes + always 1
7,                |--------->   HeroId
2,                +--------->   numSingleCards
175, 4,         + |
145, 188, 2,    + +-------------> single card part
14,               +---------^   numDoubleCards
28,           +
176, 2,       |
145, 3,       |
255, 3,       |
142, 5,       |
168, 5,       |
212, 5,       |
164, 6,       |  +----------->  double cards part
238, 6,       |
231, 7,       |
239, 7,       |
130, 176, 2,  |
136, 176, 2,  |
185, 191, 2,  +
0                 +----------->  multi cards (more than 2) count
}

PS: 我是用了ASCII Flow,效果看起来还不错. http://asciiflow.com/

数字的编码:Base 128 Varints

值得一提的是里面的卡牌编号是由1~3个byte推算出来的,比如 175, 4 是一张卡,由于采用的是short int,占用2个Byte; 145, 188, 2 是另外一张卡,占用3个byte, 而不是每个占用4个byte。这样大幅度缩减了字符串的长度,尤其是老版本卡牌宇宙卡组:) ,因为早版本的卡编号较小。这个数字是如何生成的呢?我们可以在VarInt这个类里面找到。

生成

while(value != 0)
{
    var b = value & 0x7f;
    value >>= 7;
    if(value != 0)
        b |= 0x80;
    ms.WriteByte((byte)b);
}

解析

ulong result = 0;
foreach(var b in bytes)
{
    var value = (ulong)b & 0x7f;
    result |= value << length * 7;
    if((b & 0x80) != 0x80)
        break;
}

乍一看,有点难懂,由于算的时候shift了7个bit,不难看出这个是128进制的表示方法,不过与我们常见的进制转换算法略有不同,这里使用了|= 0x80记录了一个最高有效位(most significant bit (msb) )来标识一个数字是否结束。这个自解释的信息,使得连续数字不需要其他分隔符,因为当我们读到没有MSB时,就知道这个数字标识到头了,下一个读到的新的数字的部分。这种表示方法,是倒序表示的,即低位在前面,随后读到高位然后加和得到结果。

Protocol buffers中使用了这种编码方式:https://developers.google.com/protocol-buffers/docs/encoding, 这样可以节省不少流量,不过消耗的CPU资源如何呢?

595mm in daily life

595mm

最近搬家,需要买一些家电.由于厨房只有一道约60公分的空间,在买家电的时候关注了一下尺寸. 惊奇的发现很多家电都有一个相似的尺寸:595mm.

  • 单开门冰箱
  • 滚筒洗衣机,干衣机
  • 洗碗机

我想这肯定是装修时有意如此设计的,留下一道60公分的空间来放置家电.也就是说多年前这个宽度已经成为行业标准,且各个家电行业的设计都在遵循这个标准. 由此想到计算机的硬件接口设计也是如此, 外部接口USB/SATA/VGA&HDMI多年只在进化且保持向下兼容.

不过计算机内部的接口就没有那么’友好’了,CPU/内存针脚隔代基本不兼容. 反之推想,各种电器内部也肯定经过了多重改进/改良.

都是在条条框框里面进化.