본문으로 건너뛰기

행마다 모양이 다른 묶음

시트가 코드가 되는 모습으로


「보상은 아이템이거나 화폐이거나 몬스터이다」 같은 데이터입니다. 컬럼 묶음 하나가 행마다 다른 모양을 가집니다.

모양의 목록은 시트가 아니라 선언 파일에 적습니다.

선언 파일이 먼저입니다. abstract struct 가 공통이고, extends 가 변형이며, @1 같은 번호가 그 변형을 파일에서 가리킵니다.

// The declarations the `doc-showcase` workbook's `Skill` table leaves its type cells empty for.
//
// Small on purpose: this file is shown in the documentation beside the sheet that uses it, so
// it has to be readable whole. What it has to carry is the shape - an abstract struct, two
// variants with members of their own, and one with none.

/// Something a skill does to whoever it lands on.
///
/// Abstract: a row says which kind it is in its `$type` cell, and the columns of the group are
/// every variant's members side by side.
abstract struct Effect
/// How likely it is to land, in percent. Every variant carries it, so it is one column
/// and every row fills it.
field chance int (min=0, max=100)

/// Takes health away.
struct DamageEffect extends Effect @1
/// How much it takes.
field damage int

/// Gives health back.
struct HealEffect extends Effect @2
/// How much it gives.
field amount int

/// Does nothing, and carries no members of its own.
///
/// Here because a variant with no members has to work: what is left is the name, and the
/// discriminator is the whole of what the row carries.
struct NoEffect extends Effect @3

effect.tbs

시트에는 모든 변형의 멤버를 나란히 둡니다. $type 칸이 그 행이 어느 모양인지 정하고, 그 행의 모양이 아닌 칸은 빈 칸입니다 — - 가 아닙니다. 없는 값이 아니라 그 변형이 가지지 않은 멤버이기 때문입니다.

테이블 Skill

[System.Serializable]
public partial class SkillRecord
{
#region Values
/// <summary>
/// primary index
/// </summary>
public int Index => _index;

/// <summary>
/// display name
/// </summary>
public string Name => _name;

/// <summary>
/// which shape
/// </summary>
public Effect Effect
=> _effect_value ?? (_effect_value = BuildEffect());

private Effect _effect_value;

private Effect BuildEffect()
{
switch (_effect.Type)
{
case 1:
return new DamageEffect
{
Chance = _effect.Chance,
Damage = _effect.Damage,
};
case 2:
return new HealEffect
{
Chance = _effect.Chance,
Amount = _effect.Amount,
};
case 3:
return new NoEffect
{
Chance = _effect.Chance,
};
}

// A number no variant claims. The conversion refuses one, so reaching this
// means the file was written by a build that had a variant this code does not
// - the same shape as a column added after this code was generated.
return null;
}
#endregion

/// <summary>One element of <see cref="Effect"/>.</summary>
[System.Serializable]
public struct EffectEntry
{
/// which shape
public int Type;
/// How likely it is to land, in percent. Every variant carries it, so it is one column
/// and every row fills it.
public int Chance;
/// How much it takes.
public int Damage;
/// How much it gives.
public int Amount;

public override string ToString()
{
var sb = new StringBuilder("{");
sb.Append("\"Type\":"); ToStringHelper.ToString(Type, sb);
sb.Append(",\"Chance\":"); ToStringHelper.ToString(Chance, sb);
sb.Append(",\"Damage\":"); ToStringHelper.ToString(Damage, sb);
sb.Append(",\"Amount\":"); ToStringHelper.ToString(Amount, sb);
sb.Append("}");
return sb.ToString();
}
}

#region Storage
internal int _index;
internal string _name = "";
internal EffectEntry _effect;
#endregion

#region ToString
public override string ToString()
{
var sb = new StringBuilder("{");
sb.Append("\"Index\":"); ToStringHelper.ToString(Index, sb);
sb.Append(",\"Name\":"); ToStringHelper.ToString(Name, sb);
sb.Append(",\"Effect\":"); ToStringHelper.ToString(Effect, sb);
sb.Append("}");
return sb.ToString();
}
#endregion
}

SkillTable.cs

생성된 코드는 변형마다 타입 하나를 냅니다. 어느 모양인지 확인하는 방법과 그 모양으로 받는 방법이 함께 나오고, 그 방법은 언어마다 다릅니다 — 상속이 있는 언어는 상속으로, 합 타입이 있는 언어는 그것으로 냅니다.

파일은 움직이지 않았습니다. 모든 변형의 멤버가 컬럼으로 있고 각 행이 자기 것만 채우는 것은 이미 있던 저장 방식이므로, 이 기능이 형식에 더한 것은 없습니다.