[SourcePawn] Урок 5 - DataPack

R1KO

fuck society
Сообщения
9,457
Реакции
7,786
  • Команда форума
  • #1
[SourcePawn] Урок 5 - DataPack

<- К содержанию

DataPack - производный тип от Handle, который позволяет хранить данные различных типов.
Часто нужно передать в функцию несколько значений или же массив (строку), а функция может получать только 1 параметр. Такое встречается при работе с таймерами, запросами в базу, другими запросами, которым в обратный вызов необходимо передать нужные значения.
Поэтому DataPack используется чтобы записать в него данные, передать его в нужные функции, затем извлечь из него данные и уничтожить его.
У DataPack есть особенность - данные из него нужно извлекать в той же последовательности, в которой они были записаны.

Методы для работы с DataPack можно найти здесь DataPack · datapack · SourceMod Scripting API Reference

Рассмотрим их подробнее:
  • Методы для работы с DataPack
    • DataPack - Создает объект типа DataPack
      PHP:
      DataPack hPack = new DataPack();
    • Reset - Сбрасывает позицию считывания с DataPack, чтобы начать считывание/запись сначала
      PHP:
      hPack.Reset();    // Сбросит позицию считывания
      hPack.Reset(true);    // Сбросит позицию считывания и очистит DataPack
    • IsReadable - Возвращает логическое значение возможно ли считать данные из DataPack
      PHP:
      int iValue = 0;
      if(hPack.IsReadable(4)) // В DataPack еще есть значение для считывания
      {
          iValue = hPack.ReadCell();
      }
    • Position - Свойство, которое позволяет указывать/получать позицию для считывания.
      При этом позиция определяется количеством байт записанных данных:
      PHP:
      // Напишем простой плагин:
      public void OnPluginStart()
      {
          DataPack hPack = new DataPack();
          LogMessage("Position: %i", hPack.Position);
          // Сейчас позиция = 0
          hPack.WriteCell(5);    // Запишет число 5.
          LogMessage("Position: %i", hPack.Position);
          // Каждый метод записи сперва записывает количество байт для записи, а затем данные
          // Значит чтобы записать число 5 (int) ему нужно 8 байт (2 раза по 4)
          // Сейчас позиция = 9 т.к. мы записали 8 байт и следующая запись будет начинаться с 9-го байта
          hPack.WriteFloat(7.5);
          LogMessage("Position: %i", hPack.Position);
          // Сейчас позиция = 18
          hPack.WriteString("string");
          LogMessage("Position: %i", hPack.Position);    // 6 символов по 1 байт и еще 1 нулевой, обозначающий конец строки, и того 7 символов по 1 байту = 7 байт + 1 байт + 4 (размер)
          // Сейчас позиция = 30
          hPack.WriteCell(1);
          LogMessage("Position: %i", hPack.Position);
          // Сейчас позиция = 39
      
          hPack.Reset(); // Возвращаем позицию на 0
          LogMessage("Position: %i", hPack.Position);
          int iValue = hPack.ReadCell();
          LogMessage("Position: %i -> %i", hPack.Position, iValue);
          float fValue = hPack.ReadFloat();
          LogMessage("Position: %i -> %.2f", hPack.Position, fValue);
          char szBuffer[256];
          hPack.ReadString(szBuffer, sizeof(szBuffer));
          LogMessage("Position: %i -> %s", hPack.Position, szBuffer);
          iValue = hPack.ReadCell();
          LogMessage("Position: %i -> %i", hPack.Position, iValue);
          
          delete hPack;
      }
      Результат:
      Position: 0
      Position: 9
      Position: 18
      Position: 30
      Position: 39
      // Тут сбросили позицию на 0 и начали считывать
      Position: 0
      Position: 9 -> 5
      Position: 18 -> 7.50
      Position: 30 -> string
      Position: 39 -> 1
      Визуально выглядит так:
      C-подобный:
      00 - Позиция 1
      01 --\ 
      02 ---\        Значение = 4 (указывает что хранить будем значение размером 4 байта)
      03 ---/        Размер = 4 байта (размер int)
      04 --/
      05 --\ 
      06 ---\        Значение = 5
      07 ---/        Размер = 4 байта (размер int)
      08 --/
      09 - Позиция 2
      10 --\ 
      11 ---\        Значение = 4 (указывает что хранить будем значение размером 4 байта)
      12 ---/        Размер = 4 байта (размер int)
      13 --/
      14 --\ 
      15 ---\        Значение = 7.5
      16 ---/        Размер = 4 байта (размер float)
      17 --/
      18 - Позиция 3
      19 --\ 
      20 ---\        Значение = 7 (указывает что хранить будем 7 символов по 1 байт каждый)
      21 ---/        Размер = 4 байта (размер int)
      22 --/
      23 - Символ 's'
      24 - Символ 't'
      25 - Символ 'r'
      26 - Символ 'i'
      27 - Символ 'n'
      28 - Символ 'g'
      29 - Символ '\0'
      30 - Позиция 4
      31  --\ 
      32  ---\        Значение = 4 (указывает что хранить будем значение размером 4 байта)
      33  ---/        Размер = 4 байта (размер int)
      34  --/
      35  --\ 
      36  ---\        Значение = 1
      37  ---/        Размер = 4 байта (размер float)
      38  --/
      39 - Позиция 5
      Числа - это количество байт
  • Методы для записи в DataPack
    • WriteCell - Записывает в DataPack ячейку (int, Handle и любое другое 4-х байтовое значение)
      PHP:
      hPack.WriteCell(5);
    • WriteFloat - Записывает в DataPack число с плавающей точкой (дробное)
      PHP:
      hPack.WriteFloat(7.3);
    • WriteString - Записывает в DataPack строку
      PHP:
      hPack.WriteString("моя строка");
    • WriteFunction - Записывает в DataPack указатель на функцию
      PHP:
      function MyFunc = GetFunctionByName(null, "MyFunction");
      if(MyFunc != INVALID_FUNCTION)
      {
          hPack.WriteFunction(MyFunc);
      }
  • Методы для чтения из DataPack
    • ReadCell - Получает из DataPack ячейку
      PHP:
      int iValue = hPack.ReadCell();
    • ReadFloat - Получает из DataPack число с плавающей точкой (дробное)
      PHP:
      float fValue = hPack.ReadFloat();
    • ReadString - Получает из DataPack строку
      PHP:
      char szBuffer[256];
      hPack.ReadString(szBuffer, sizeof(szBuffer));
    • ReadFunction - Полуачет из DataPack указатель на функцию
      PHP:
      function MyFunc = hPack.ReadFunction(MyFunc);
Когда DataPack больше не нужен его обязательно нужно удалить:
PHP:
delete hPack;

Пример использования DataPack:

Передача DataPack в таймере:
PHP:
public void OnPluginStart()
{
    DataPack hPack = new DataPack();
    hPack.WriteCell(5);
    hPack.WriteFloat(7.5);
    hPack.WriteString("string");
    CreateTimer(5.0, Timer_ReadDataPack, hPack);
}

public Action Timer_ReadDataPack(Handle hTimer, Handle hDataPack)
{
    DataPack hPack = view_as<DataPack>(hDataPack);    // Методы работают только с типом DataPack, а не Handle
    hPack.Reset(); // Возвращаем позицию на 0
    int iValue = hPack.ReadCell();
    LogMessage("Position: %i -> %i", hPack.Position, iValue);
    float fValue = hPack.ReadFloat();
    LogMessage("Position: %i -> %.2f", hPack.Position, fValue);
    char szBuffer[256];
    hPack.ReadString(szBuffer, sizeof(szBuffer));
    LogMessage("Position: %i -> %s", hPack.Position, szBuffer);
    delete hPack;
}

Результат:
Position: 9 -> 5
Position: 18 -> 7.50
Position: 30 -> string
 
Последнее редактирование:

AlmazON

Не путать с самим yand3xmail
Сообщения
5,099
Реакции
2,756
Когда DataPack больше не нужен его обязательно нужно удалить
Наверное, ниже стоит сразу дополнить примером CreateDataTimer или рассказать про аналогичный флаг таймера - TIMER_DATA_HNDL_CLOSE, когда удалять ничего не нужно.
 

R1KO

fuck society
Сообщения
9,457
Реакции
7,786
  • Команда форума
  • #3
@AlmazON, я хочу это сделать в след. уроке (он про таймеры), в нем буду ссылаться на этот. Я потому и хотел датапаки написать до таймеров
 

SAZONISCHE

Участник
Сообщения
405
Реакции
232
@AlmazON, я хочу это сделать в след. уроке (он про таймеры), в нем буду ссылаться на этот. Я потому и хотел датапаки написать до таймеров
Очень хорошая идея (может русскоязычные вики замутите?)
 

R1KO

fuck society
Сообщения
9,457
Реакции
7,786
  • Команда форума
  • #7
Визуально выглядит так:
C-подобный:
00 - Позиция 1
01 --\ 
02 ---\        Значение = 4 (указывает что хранить будем значение размером 4 байта)
03 ---/        Размер = 4 байта (размер int)
04 --/
05 --\ 
06 ---\        Значение = 5
07 ---/        Размер = 4 байта (размер int)
08 --/
09 - Позиция 2
10 --\ 
11 ---\        Значение = 4 (указывает что хранить будем значение размером 4 байта)
12 ---/        Размер = 4 байта (размер int)
13 --/
14 --\ 
15 ---\        Значение = 7.5
16 ---/        Размер = 4 байта (размер float)
17 --/
18 - Позиция 3
19 --\ 
20 ---\        Значение = 7 (указывает что хранить будем 7 символов по 1 байт каждый)
21 ---/        Размер = 4 байта (размер int)
22 --/
23 - Символ 's'
24 - Символ 't'
25 - Символ 'r'
26 - Символ 'i'
27 - Символ 'n'
28 - Символ 'g'
29 - Символ '\0'
30 - Позиция 4
31  --\ 
32  ---\        Значение = 4 (указывает что хранить будем значение размером 4 байта)
33  ---/        Размер = 4 байта (размер int)
34  --/
35  --\ 
36  ---\        Значение = 1
37  ---/        Размер = 4 байта (размер float)
38  --/
39 - Позиция 5
Числа - это количество байт

Поковырял исходники и понял откуда берется еще 1 байт. Он обозначает тип хранимых данных:
C-подобный:
enum DataPackType {
		Raw,
		Cell,
		Float,
		String,
		Function
	};

PHP:
void CDataPack::PackCell(cell_t cell)
{
	CheckSize(sizeof(char) + sizeof(size_t) + sizeof(cell_t));

	*(char *)m_curptr = Cell;
	m_curptr += sizeof(char);

	*(size_t *)m_curptr = sizeof(cell_t);
	m_curptr += sizeof(size_t);

	*(cell_t *)m_curptr = cell;
	m_curptr += sizeof(cell_t);

	m_size += sizeof(char) + sizeof(size_t) + sizeof(cell_t);
}
--- Добавлено позже ---
О изменениях датапаков в sm 1.10
upload_2018-2-11_2-7-40.png
 
Последнее редактирование:

Palonez

бб братки
Сообщения
3,035
Реакции
1,838
Как быть c ArrayList например?
Пытался приводить в int и обратно при распаковке, не получается :(


C-подобный:
ArrayList A, B, C;
A = B = C = CreateArray(64);

DataPack dp = CreateDataPack();

CreateTimer(1.0, CBTimer, dp, TIMER_REPEAT);

dp.WriteCell(view_as<int>(A));
dp.WriteCell(view_as<int>(B));
dp.WriteCell(view_as<int>(C));
...

public Action CBTimer(Handle hTimer, DataPack dp)
{
    dp.Reset(); // Invalid Datapack Handle
    ArrayList _1 = view_as<ArrayList>(dp.ReadCell());
    ArrayList _2 = view_as<ArrayList>(dp.ReadCell());
    ArrayList _3 = view_as<ArrayList>(dp.ReadCell());
    delete dp;
// Мне сюды надо передать мои массивы
    return Plugin_Continue;
}
 
Последнее редактирование:

xstage

🏹
Сообщения
726
Реакции
754
Как быть c ArrayList например?
Пытался приводить в int и обратно при распаковке, не получается :(


C-подобный:
ArrayList A, B, C;
A = B = C = CreateArray(64);

DataPack dp = CreateDataPack();

CreateTimer(1.0, CBTimer, dp, TIMER_REPEAT);

dp.WriteCell(view_as<int>(A));
dp.WriteCell(view_as<int>(B));
dp.WriteCell(view_as<int>(C));
...

public Action CBTimer(Handle hTimer, DataPack dp)
{
    dp.Reset();
    ArrayList _1 = view_as<ArrayList>(dp.ReadCell());
    ArrayList _2 = view_as<ArrayList>(dp.ReadCell());
    ArrayList _3 = view_as<ArrayList>(dp.ReadCell());
    delete dp;
// Мне сюды надо передать мои массивы
    return Plugin_Continue;
}
Что именно не получается? Ошибки при компиляции или что?
 

Palonez

бб братки
Сообщения
3,035
Реакции
1,838
Что именно не получается? Ошибки при компиляции или что?
Инвалид хендл датапака на 15 строке этого кода
Сообщения автоматически склеены:

а на кой тебе в таймер передавать столько инфы?
Нада :ㅖ
В этих массивах передаются строки в таком виде hello:2, я пока не додумался иначе реализовать свою хотелку
 

xstage

🏹
Сообщения
726
Реакции
754
Инвалид хендл датапака на 15 строке этого кода
Сообщения автоматически склеены:


Нада :ㅖ
В этих массивах передаются строки в таком виде hello:2, я пока не додумался иначе реализовать свою хотелку
Ну как минимум TIMER_REPEAT, а хендл датапака удаляется в обратном вызове таймера. Соответственно в последующих вызовах хендла нет :/
 

Grey83

не пишу плагины с весны 2022
Сообщения
8,606
Реакции
5,096
В этих массивах передаются строки в таком виде hello:2
передавай строки с разделителем, а потом разбивай их с помощью ExplodeString()

@Palonez, или так попробуй:
C-подобный:
public void OnPluginStart()
{
    ArrayList A, B, C;
    A = CreateArray(64), B = CreateArray(64), C = CreateArray(64);
    DataPack dp = new DataPack();
    CreateDataTimer(1.0, Timer_Something, dp, TIMER_DATA_HNDL_CLOSE);
    dp.WriteCell(A);
    dp.WriteCell(B);
    dp.WriteCell(C);
}

public Action Timer_Something(Handle timer, DataPack dp)
{
    dp.Reset();
    ArrayList _1 = dp.ReadCell();
    ArrayList _2 = dp.ReadCell();
    ArrayList _3 = dp.ReadCell();
    delete dp;

    return Plugin_Stop;
}
но если ты юзаешь флаг TIMER_REPEAT, то будь бобр делай эти ArrayList глобальными и перестань использовать в таком таймере DataPack.

в общем, не видя что ты и зачем пытаешься передать, я мало что подсказать могу
 
Последнее редактирование:

Palonez

бб братки
Сообщения
3,035
Реакции
1,838
Да, проблема была в TIMER_REPEAT, первая передача была успешной, но я этого не заметил. Спасибо
 

xstage

🏹
Сообщения
726
Реакции
754
передавай строки с разделителем, а потом разбивай их с помощью ExplodeString()

@Palonez, или так попробуй:
C-подобный:
public void OnPluginStart()
{
    ArrayList A, B, C;
    A = CreateArray(64), B = CreateArray(64), C = CreateArray(64);
    DataPack dp = new DataPack();
    CreateDataTimer(1.0, Timer_Something, dp, TIMER_DATA_HNDL_CLOSE);
    dp.WriteCell(A);
    dp.WriteCell(B);
    dp.WriteCell(C);
}

public Action Timer_Something(Handle timer, DataPack dp)
{
    dp.Reset();
    ArrayList _1 = dp.ReadCell();
    ArrayList _2 = dp.ReadCell();
    ArrayList _3 = dp.ReadCell();
    delete dp;

    return Plugin_Stop;
}
но если ты юзаешь флаг TIMER_REPEAT, то будь бобр делай эти ArrayList глобальными и перестань использовать в таком таймере DataPack.

в общем, не видя что ты и зачем пытаешься передать, я мало что подсказать могу
Не совсем так. Вот как сток CreateDataTimer выглядит. Незачем создавать датапак он сам создаст, флаг так же бессмысленно ставить.
C-подобный:
stock Handle CreateDataTimer(float interval, Timer func, Handle &datapack, int flags=0)
{
    datapack = new DataPack();
    flags |= TIMER_DATA_HNDL_CLOSE;
    return CreateTimer(interval, func, datapack, flags);
}

Да, проблема была в TIMER_REPEAT, первая передача была успешной, но я этого не заметил. Спасибо
Вообще решение вот такое:

C-подобный:
public void OnPluginStart()
{
    ArrayList A, B, C;
    A = B = C = CreateArray(64);

    DataPack dp;
    CreateDataTimer(1.0, CBTimer, dp, TIMER_REPEAT);

    dp.WriteCell(view_as<int>(A));
    dp.WriteCell(view_as<int>(B));
    dp.WriteCell(view_as<int>(C));
}

Action CBTimer(Handle timer, DataPack dpc)
{
    static int count;

    dpc.Reset();
    ArrayList _1 = view_as<ArrayList>(dpc.ReadCell());
    ArrayList _2 = view_as<ArrayList>(dpc.ReadCell());
    ArrayList _3 = view_as<ArrayList>(dpc.ReadCell());

    // Мне сюды надо передать мои массивы
    return Plugin_Continue;
}

При возврате Plugin_Stop в обратном вызове таймера, хендел датапака удалится сам
 
Сверху Снизу