Перейти к основному содержимому

Пулы — строка ссылается на целую запись

Генератор выдаёт значение. Последовательность — это столбец таких значений. Этого хватает почти на всё, что нужно конфигу, — а потом упирается в стену.

Две тысячи пациентов принимают тридцать врачей. В строке пациента нужны имя, фамилия и кабинет врача. Три последовательности рядом дают три независимых розыгрыша, и в строке оказывается имя одного врача рядом с кабинетом другого. Данные выглядят правдоподобно и при этом бессмысленны: Dr. John Williams, room 118, где такого врача не существует.

Не хватает мысли, что врач — это не значение, а запись, и тридцать таких записей существуют до того, как появится первый пациент. <pool> — это и есть такая таблица.

<tdc>
<env count="8" seed="clinic" local="en">
<pool name="Doctors" count="4">
<sequence name="firstName"><gen type="template" value="person.male.firstName"/></sequence>
<sequence name="lastName"><gen type="template" value="person.lastName"/></sequence>
<sequence name="room"><gen type="number" value="100..199"/></sequence>
</pool>

<sequence name="Patient"><gen type="template" value="person.female.firstName"/></sequence>
<sequence name="Seen"><gen type="pool" value="Doctors"/></sequence>
</env>
<block>
<line><data>${{Patient}} -> Dr. ${{Seen.firstName}} ${{Seen.lastName}}, room ${{Seen.room}}</data></line>
</block>
</tdc>
./run clinic.tdc
Barbara -> Dr. James Johnson, room 100
Mary -> Dr. Michael Smith, room 186
Margaret -> Dr. Robert Brown, room 148
Jennifer -> Dr. John Williams, room 154
Elizabeth -> Dr. Michael Smith, room 186
Patricia -> Dr. Michael Smith, room 186
Susan -> Dr. John Williams, room 154
Linda -> Dr. James Johnson, room 100
Вывод показан для примера

Значения получены с фиксированным seed, то есть воспроизводимы, но конкретные строки могут отличаться между версиями ядра. Смотрите на них как на пример формы, а не как на гарантию.

Четыре врача обслуживают восемь пациентов, и три поля каждого врача держатся вместе. Джеймс Смит сидит в кабинете 197 в каждой строке, где он появляется, потому что строка выбрала Смита, а не «имя, фамилию и кабинет».

Четыре члена, восемь строк. Каждая строка забирает целую строку таблицы.
  • Aпул: четыре члена, построенные один раз до прогона
  • Bсгенерированные строки
  • Cодин выбор на строку — обе ячейки строки всегда от одного и того же члена

Происходит две вещи, именно в таком порядке

  1. Строится пул. До того как появится хоть одна строка, TDC создаёт count членов. Это происходит один раз — и для прогона на 8 строк, и на 8 миллиардов.
  2. Каждая строка берёт одного члена. <gen type="pool"> даёт своей последовательности одного целого члена на строку и публикует все его поля под именем Ссылка.поле.

Всё остальное на этой странице следует из этих двух предложений.

Кратко

<pool> живёт прямо в <env>, рядом с <sequence>. Напрямую его не читают: из него разыгрывает последовательность.

У <pool>ОбязателенЧто задаёт
nameдаИмя, которое <gen type="pool"> называет в value
countдаСколько членов в таблице
commentнетСвободный комментарий
У <gen type="pool">ОбязателенЧто задаёт
valueдаИз какого пула разыгрывать
filterнетИз каких членов эта строка может выбирать — Отбор
ifнетДостанется ли строке член вообще — Отбор

Пул — это миниатюрный <env>

Это то решение в устройстве, которое стоит знать: оно отвечает на большинство вопросов до того, как вы их зададите. Тело пула строится тем же механизмом, что и <env> конфига, только вместо числа строк работает число членов.

Поэтому всё, что вы уже знаете, действует и внутри пула:

Внутри пулаОзначает
<sequence>поле каждого члена
<mix>поле, разделённое точным percent — по ЧЛЕНАМ
<switch>поле, выведенное из другого поля того же члена
<uniq> · uniq="true"члены, которые должны отличаться друг от друга
<distinct>два поля одного члена, которые должны отличаться
if=поле, которое есть только у части членов
parent=поле, построенное по тем членам, которых выбрало другое поле
<compute>поле, вычисленное из других полей члена

Чего в пуле быть не может — это вывода: ни <block>, ни теги обрамления. Пул это таблица, которую читают другие столбцы, а не то, что пишется в файл. Ещё в нём не может быть другого <pool> — пулы остаются плоскими таблицами, и один ссылается на другой, а не вкладывается в него.

Два имени и точка

Имя есть у пула и у последовательности, которая из него разыгрывает. Это разные вещи:

<pool name="Doctors" count="4"></pool>

<sequence name="Seen"><gen type="pool" value="Doctors"/></sequence>
  • Doctorsтаблица. В <block> на неё никто не ссылается.
  • Seenстолбец, в котором на каждой строке лежит один врач. Его поля читаются как ${{Seen.firstName}}.

Из одного пула могут разыгрывать две последовательности, и выбирают они независимо: Seen и Referred в одной строке дадут двух разных врачей — обычно это и нужно.

Прочитать ссылку без поля нельзя — печатать нечего:

./run clinic.tdc
error[TDC229]: "Seen" draws a whole member from a pool — it has no value of its own to print
--> clinic.tdc:8:16
|
8 |   <block><line><data>${{Seen}}</data></line></block>
|                ^^^^^^^^^^^^^^^^^^^^^^
|
note: Read a field: ${{Seen.name}}.

Один выбор на строку

В последовательности одно значение на строку. Ссылка на пул — это последовательность, значит в ней один член на строку, и все ${{Seen.…}} в этой строке читают одного и того же.

Это правило и есть смысл конструкции. Из него же следует, почему «Дмитрий Иванова» пул выдать не может: пол, имя и фамилия — поля одного члена, и строка, взявшая члена, взяла все три.

Выбор равномерный по членам — это розыгрыш, а не график дежурств, потому что долей никто не объявлял; см. объявленные доли или розыгрыш. Он ещё и адресуемый: строка 900 000 000 находит своего врача без того, чтобы 899 999 999 предыдущих вообще существовали. Поэтому пул одинаково дёшев на всех движках.

Доли внутри пула

Раз пул строится обычным механизмом, percent работает внутри него так же, как на верхнем уровне. Тридцать процентов врачей — хирурги:

<pool name="Doctors" count="10">
<mix name="role" percent="30,70">
<case><gen type="text" value="surgeon"/></case>
<case><gen type="text" value="therapist"/></case>
</mix>
<sequence name="name"><gen type="template" value="person.lastName"/></sequence>
</pool>

Доля считается от десяти членов, а не от строк. Хирургов трое; как часто к этим троим попадают — зависит от того, как разыгрываются строки, и от filter, который превращает «трое из врачей — хирурги» в «пациент, которому нужен хирург, к хирургу и попадает».

Члены, отличающиеся друг от друга

uniq внутри пула означает то же, что и снаружи: ни у каких двух членов нет одинакового значения.

<pool name="Rooms" count="6">
<sequence name="number" uniq="true"><gen type="number" value="100..199"/></sequence>
</pool>

Шесть разных номеров кабинетов, розыгрыш без возврата. Пулу, члены которого задуманы как разные вещи, обычно нужно это хотя бы на одном поле — иначе два члена могут выйти одинаковыми, и строка их не различит.

Собственный сид пула

Пул не разыгрывается из основного потока прогона. У него свой производный сид:

<сид прогона> + "#pool:" + <имя пула>

Пул Doctors при seed="main" строится из main#pool:Doctors. Атрибута seed у <pool> нет — весь механизм в этом выводе, а попытка его написать сообщается (TDC015).

Почему выводится, а не берётся. Если бы пул тянул из основного потока, его добавление сдвинуло бы все колонки, объявленные ниже: id остались бы, а возрасты, имена и даты уехали, и вчерашний снимок перестал бы совпадать по причине, которую в диффе не видно. Производный сид делает пул невидимым для всего, что он не кормит.

Два следствия, которые стоит знать:

  • Меняете seed прогона — меняется и пул. Это и задумано: одно число по-прежнему воспроизводит весь прогон целиком.
  • Переименование пула меняет его состав, потому что имя входит в производный сид. Переименование здесь не косметика; если важен конкретный набор членов, имя лучше не трогать.

Размер

Пул держится в памяти весь прогон — именно поэтому обращение к нему из строки бесплатно. На эталонной реализации замерено: член с четырьмя полями стоит около 320 байт.

ЧленовПримерноЧто происходит
до 100 000до ~29 МБничего, работает
больше 100 000TDC234, предупреждение
больше 1 000 000больше ~290 МБTDC235, отказ

Оба сообщения заканчиваются одинаково, потому что причина обычно одна: если имелось в виду число строк, то это count у <env>.

Потолок касается таблицы, а не прогона. Пул из тридцати врачей обслуживает миллиард строк пациентов ровно так же дёшево, как тысячу, и работает на всех движках.

Куда дальше

  • Отбор через filter — из каких членов может выбирать строка и чем это отличается от if
  • Связывание пулов — несколько пулов, пул, разыгрывающий из другого пула, и цепочки в три уровня