LINUX.ORG.RU

Сообщения KivApple

 

Python и миграции БД

Форум — Development

Какие есть альтернативы Alembic для организации миграции схемы БД в веб-приложениях на Python?

Я хочу миграции в виде SQL-скриптов (с опциональной возможностью иногда вызывать Python-код для сложных ситуаций), а не попыток писать недо-SQL на Python. Мне также не нужны миграции в обратную (down) сторону, только вперёд (эта функция может быть в библиотеке, но не надо заставлять меня писать пустые миграции в обратную сторону, когда я не хочу). Зато нужна поддержка PostgreSQL. Ещё было бы классно, если бы библиотека миграции поддерживала asyncio. Да, для самой миграции асинхронность ничего не даёт, но это позволяет не держать в зависимостях две реализации драйвера БД - асинхронный и синхронный.

Аналог из мира Java - Flyway.

 , ,

KivApple
()

Пометить ВСЕ письма как прочитанные

Форум — General

Имеется ящик Gmail. И там скопились буквально тысячи непрочитанных писем (а если быть точным - более 14 тысяч писем). Я их в любом случае никогда уже не прочитаю (да и информация там неактуальная), но из-за них счётчик новых писем не отражает реально новые письма, которые мне нужно прочитать (вообще, это следствие в том числе всяких мусорных рассылок от кучи сайтов, но я недавно навёл порядок и отписался практически от всего хлама, так что теперь мне поступает не слишком много писем в день).

Можно ли как-то через интерфейс Gmail отметить как прочитанные ВСЕ письма или, например, ВСЕ письма старше какой-то даты? Не выделять по 50 штук (это всё равно больше сотни страниц) и жать «отметить как прочитанные», а вот прям все разом.

Если Gmail на такое не способен (хотя, уверен, весьма распространённый use-case), то может быть есть IMAP-совместимый почтовый клиент, который способен на такое? Или даже отдельная утилита вида «дай мне параметры подключения к IMAP и я отмечу прочитанными все письма, которые найду».

Вообще, было бы удобно, если бы письма отмечались как прочитанные после какого-то таймаута. Если у меня не дошли руки прочитать письмо за 1-2 недели, значит не дойдут уже никогда, а потом и информация из письма устареет. Но это уже совсем утопия.

 ,

KivApple
()

CMake: Получить список определений препроцессора

Форум — Development

В ходе компиляции проекта CMakeLists.txt запускает мой Python скрипт, который парсит некоторые исходники и извлекает оттуда некоторую мета-информацию (которая затем используется для генерации части других исходников проекта). Проблема в том, что для корректного парсинга исходников нужно знать объявленные директивы препроцессора. Например, в релизном билде CMAKE добавляет определение NDEBUG. Также какие-то определения могут добавлять используемые библиотеки и, наконец, я сам через add_definitions и target_add_definitions.

Хочется получить полный набор определений, которые добавлены именно через CMake, дефолтные определения компилятора не интересны, потому что я выполняю препроцессинг его же силами (с помощью опции -E) и с ними проблем нет.

get_property(GLOBAL_COMPILE_DEFINITIONS GLOBAL PROPERTY COMPILE_DEFINITIONS)
get_property(DIR_COMPILE_DEFINITIONS DIRECTORY "${CMAKE_SOURCE_DIR}" PROPERTY COMPILE_DEFINITIONS)
get_property(TARGET_COMPILE_DEFINITIONS TARGET MyProgram PROPERTY COMPILE_DEFINITIONS)
message(STATUS "COMPILE_DEFINITIONS ${TARGET_COMPILE_DEFINITIONS} ${DIR_COMPILE_DEFINITIONS} ${GLOBAL_COMPILE_DEFINITIONS}")

Выдаёт мне объявления добавленные с помощью add_definitions и target_add_definitions, но игнорирует встроенные определения CMake такое как NDEBUG в релизной сборке. Можно ли как-то получить NDEBUG или только хардкодить проверку типа билда? Насколько мой способ получения в целом полный, не пропустит ли он как-нибудь хитро добавленное определение какой-нибудь гипотетической библиотекой?

 ,

KivApple
()

Приложения и сервисы для изучения грамматики иностранного языка

Форум — Talks

Вот учу я, допустим, французский. Есть всякие приложения типа Duolingo, Busuu. У них сильная сторона - изучение лексики, разбор конкретных ситуаций вида «что говорить, если я попал в аэропорт» и «как рассказать как я провёл лето». Просмотр фильмов учит понимать на слух и опять же лексике. Чтение книг учит в первую очередь лексике. А есть ещё такая штука как грамматика. С каким окончанием надо писать глагол partir в третьем лице единственном числе во времени imparfait. И вот такие штуки можно только выучить.

Однако, я до сих пор не встречал комплексных приложений на эту тему. С моей точки зрения идеальное приложение должно работать так:

- Хочу поучить числительные - Тебе показывают число записанное цифрами и ты должен записать его буквами. Число может быть большим - с сотнями, тысячами и даже миллионами. Число случайное, а не один и тот же тест из десяти вопросов. Может быть обратная задача - тебе показывают число записанное буквами и предлагают написать его цифрами.

- Хочу поучить спряжения глаголов для уровня A1/A2/B1/B2/etc (от уровня зависит набор доступных времён, либо можно просто выбрать какие времена интересны). - Тебе показывают случайно выбранный инфинитив глагола, личное местоимение и время. Ты должен написать глагол правильно согласовав. На лёгком уровне сложности могут подсказать группу глагола, на сложном должен сам понять.

То же самое для сопряжения прилагательных (род и число) и т. п.

С алгоритмической точки зрения нет проблем составлять вопросы из кусочков. Достаточно иметь размеченную БД тех же глаголов с указанием правил спряжения (на том же wikitonary есть такая информация, плюс куча других сайтов). Но... Таких приложений нет. Предлагают учить лексику, а грамматика сама как-нибудь когда-нибудь подтянется. Но ведь она тоже важна.

Можно найти учебник с упражнениями, но эти упражнения потом должен кто-то проверять, а у приложений и сервисов плюс в мгновенной реакции правильно/неправильно. Это гораздо эффективнее для обучения.

Может быть уже есть такие штуки? А то у меня уже от безысходности часаться руки по мере изучения языка писать процедурные генераторы вопросов такого вида.

P. S. Только не надо говорить, что грамматика не нужна - есть вполне конкретные ситуации, когда она нужна 100% - сдача языкового экзамена для поступления в вуз, трудоустройства, получения ВНЖ/ПМЖ/гражданства и т. д.

P. P. S. Английский ещё относительно легко учится, там одна таблица неправильных глаголов, из которой активно используется меньше половины. А времена строятся как различные комбинаиции употребления to be, have и инфинитива глагола с максимум с парой вариантов концовок. А в том же французском есть группы глаголов, рода, времена и это всё с кучей исключений для каждого случая.

 иностранные языки

KivApple
()

Обновление порядка элементов в таблице с помощью SQL

Форум — Development

Допустим, есть вот такая табличка (для простоты опустим индексы и т. п.):

CREATE TABLE items (
    id BIGSERIAL PRIMARY KEY,
    category_id BIGINT NOT NULL,
    name VARCHAR NOT NULL,
    position INT NOT NULL
);
Для отображения юзеру элементы из неё выбираются вот таким запросом:
SELECT name FROM items WHERE category_id = ? ORDER BY position
То есть порядок элементов внутри категории жёстко задан с помощью поля position (можно, кстати, навесить уникальный индекс на category_id + position, чтобы быть уверенным в детерменизме выборки).

Встаёт вопрос как дать пользователю возможность менять порядок элементов. Допустим, на фронте реализован интерфейс с drag'n'drop и кнопкой «Сохранить изменения», при нажатию на которую бек получает category_id и список id всех элементов этой категории в новом порядке. Теперь беку нужно обновить значения полей position в БД (элементов в каждой категории не очень много, так что мне кажется допустимым перезаписать position у всех элементов в заданной категории).

Я пока вижу тут только решение в лоб с отдельным UPDATE для каждой записи, а значения position вычислять в коде (банальный цикл for по индексу в массиве id c фронта и использовать счётчик цикла). Но мне кажется такой подход не очень эффективным, так как будет N запросов, к тому же он плохо уживается с уникальным индексом на category_id + position (либо такой индекс надо убрать, либо использовать deferred индексы и т. п.).

Есть ли какие-то решения лучше?

P. S.: СУБД PostgreSQL

 ,

KivApple
()

Тесты для plain C

Форум — Development

Посоветуйте годную библиотеку для unit-тестирования plain C проекта.

Требования:

- Описываю набор функций напичканных различными assert, после сборки тестов получаю бинарник, который прогонит все тесты и расскажет какие упали (очень желательно, если прямо выведет пары ожидаемых значений и реально полученных значений), при этом не останавливаясь на первом упавшем тесте (было бы круто каждый тест запускать в отдельном процессе, чтобы даже сегфолт попал в отчёт тестирования, желательно со стектрейсом, если проект собран с отладочными символами).

- Поддержка CMake (чтобы просто подключить проект через add_subdirectory или вообще инклюд скрипта). Либо библиотека должна не иметь стадию конфигурирования и подключаться простым прописываанием include и добавлением нескольких исходных файлов в проект.

- Кроссплатформенность - то есть как минимум Linux и Windows.

- Не нужно ничего ставить в систему, всё должно собираться вместе со сборкой основного проекта

- Ещё бонусом было бы переопределить malloc (через weak/strong символы, ld preload, глобальный макрос, да хоть как) и, соответственно, после окончания теста проверять, что вся память освобождена. Но такое, наверное, вряд ли где-то реализовано. Разве что весь тестовый бинарник запускать под valgrind, но это вариант только для Linux, зато отловит и более сложные ошибки работы с памятью.

Да, на базовом уровне это пишется за 15 минут путём описания пары макросов (определения теста и сам ASSERT), но хочется всяких плюшек (та же обработка сегфолтов), а если в них углубляться, то уже далеко не на 15 минут выйдет.

 ,

KivApple
()

Быстрый Set на C

Форум — Development

Мне нужна структура данных похожая на Set. Она должна обладать следующими свойствами:

- В ней хранятся числа от 0 до 4095.

- В неё можно добавлять числа, при этом повторное добавление того же самого числа должно игнорироваться (то есть поддерживаться уникальность элементов). Удаление одиночных элементов не обязательно, нужна только операция полной очистки.

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

Эта структура должна работать быстро, потому что будет много-много раз в секунду цикл заполнение-итерирование-очистка, то есть вставка за O(1), итерирование по всем элементам за O(N), очистка желательно за O(1).

На первый взгляд приходят в голову битовые карты (ведь множество значений элементов небольшое), однако они, как мне кажется, не проходят по условию «быстрое итерирование и очистка». Есть ли какие-то ещё варианты? Подойдёт как алгоритм, так и готовая plain C библиотека. Или на таком количестве элементов все альтернативы будут иметь такую же производительность?

 ,

KivApple
()

Сериализация данных в plain C

Форум — Development

Как нынче модно в plain C выполнять сериализацию/десериализацию данных?

Ну то есть у меня какая-то структура, я хочу её записать в файл/отправить по сети. Значит мне нужно:

- Убрать выравнивание

- Переставить байтики под endiness (если не совпадает с endiness сериализации)

- Сделать то же самое для вложенных структур

- Вытащить динамические массивы (в самой структуре представлены двумя полями - длина и первый элемент) из кучи и положить их линейно с остальными полями

То есть прожиточный минимум - это либа, которая работает с байтовым буфером и имеет функции вида buffer_write_int8/buffer_write_int16/buffer_write_float32/buffer_read_int32/etc. Но если бы было можно пропарсить заголовочник с используемыми структурами (для меня ок, если будет требование все сериализуемые структуры держать в отдельных заголовочниках) и сгенерировать сериализаторы, либо наоборот сгенерировать структуры и сериазаторы из описания в каком-то другом формате, то было бы вообще шикарно.

Что я не хочу? Не хочу никакого оверхеда и метаинформации в сериализованных данных. Если я сериализую структуру из 3 int32_t, значит выходной буфер должен иметь размер строго 12 байт. Максимум можно опциональную фичу, отдельно включаемую для определённых структур, чтобы перед структурой писался её размер (типа чтобы при попытке десериализации старой версии структуры, новые поля проинициализировались нулями - подразумевается, что поля могут быть добавлены только в конец). Но я и без неё отлично проживу. И обязательно опциональная фича (потому что некоторые структуры точно не изменятся).

Так что всякие Protobuf не подходят, потому что помимо самих данных пишут всякую метаинформацию.

Удачный пример такой либы из плюсов - bitsery, но в plain C нет шаблонов и поэтому нужен другой подход к либе (кодогенерация).

 ,

KivApple
()

Rust и создание больших массивов

Форум — Development

Вот этот очень простой код потенциально легко вызовет stack overflow (если нет, то надо просто увеличить 16777216), хотя не должен (мы ведь на самом деле выделяем место в куче в итоге).

#[derive(Copy, Clone)]
pub struct Item {
    a: i32,
    b: i32
}

pub struct Items {
    items: [Item; 16777216]
}

impl Items {
    pub fn new() -> Items {
        Items {
            items: [Item { a: 0, b: 1 }; 16777216]
        }
    }
}

fn foo() -> Box<Items> {
    Box::new(Items::new())
}

Пруф: https://rust.godbolt.org/z/8sWsoKojx

Для Ъ: Массив сначала создаётся на стеке, а только потом выделяется память и происходит memcpy в кучу. Максимальные оптимизации не спасут.

К тому же не забываем про оверхед копирования. Я готов поверить разработчикам Rust, что копирование при создании структур из нескольких сотен байт не создаст заметной просадки, но я верю, что пример выше будет статистически значимо работать медленнее уже на нескольких тысячах элементов, а не обязательно 16 миллионов.

Как принято создавать в Rust такие массивы? unsafe или есть решения получше?

Мне нравится Rust последнее время, сколько я к нему присматриваюсь, но вот такая очевидная мелочь как copy elision не предусмотрена для типа системного языка... Или я просто всё делаю не так и Items::new надо писать как-то иначе?

 ,

KivApple
()

Rust и конкурентная структура данных с RAII

Форум — Development

Я хочу сделать структуру данных, которая хранит в себе элементы идентифицируемые ключом (для простоты примера здесь в качестве ключа выступает число). При этом я хочу, чтобы обращение к несуществующему элементу создавало его с нужным ключом, а с разными элементами можно было работать параллельно из разных потоков, при этом используя RAII (блокирующе получаем обёртку для работы с элементом, потому что HashMap однопоточный, к тому же процесс создания несуществующего элемента это потенциальный race condition, а затем через обёртку можем работать с нашим элементом уже вне зависимости от поведения других потоков, при этом после поиска/создания должен быть заблокирован уже только один конкретный элемент).

Максимально наивная реализация:

use std::sync::{Mutex, MutexGuard};
use std::collections::HashMap;
use std::collections::hash_map::Entry;

struct Item {
    a: i32,
    b: i32
}

struct ItemStorage {
    items: Mutex<HashMap<i32, Box<Mutex<Item>>>>
}

struct ItemRef<'a> {
    item: MutexGuard<'a, Item>
}

// Всякие new и прочие идеоматические вещи опущены для простоты
impl ItemStorage {
    pub fn item(&mut self, key: i32) -> ItemRef {
		let mut items_lock = self.items.lock().unwrap();
		let item = match items_lock.entry(key) {
			Entry::Occupied(v) => v.into_mut(),
			Entry::Vacant(v) => {
				v.insert(Box::new(Mutex::new(Item {
					a: 0, b: 1
				})))
			}
		};
		ItemRef {
			item: item.lock().unwrap()
		}
	}
}

fn main() {
    let mut storage = ItemStorage {
        items: Mutex::new(HashMap::new())
    };
    // Код ниже можно запускать из разных потоков как с одинаковыми, так и с разными ключами
    let mut item_ref = storage.item(100);
    item_ref.item.a += 1;
}

Ожидаемо Rust чувствует, что здесь что-то не так:

error[E0515]: cannot return value referencing local variable `items_lock`
  --> src/main.rs:29:3
   |
22 |           let item = match items_lock.entry(key) {
   |                            ---------- `items_lock` is borrowed here
...
30 | /         ItemRef {
31 | |             item: item.lock().unwrap()
32 | |         }
   | |_________^ returns a value referencing data owned by the current function

error: aborting due to previous error

Как обрабатываются правильно такие ситуации? Или подобный RAII надо реализовать с помощью каких-нибудь возможностей unsafe?

 ,

KivApple
()

Воксельный движок на Rust

Форум — Development

Как вы бы реализовали хранение вокселей «а-ля Minecraft» на Rust?

- Каждый воксель имеет тип, который задаёт его поведение (начиная от построения меша для рендеринга и заканчивая реакцией на действия игрока и т. д.)

- Помимо типа у каждого вокселя может быть некоторое состояние. Структура состояния зависит от типа (не всем типам вокселей вообще оно нужно). Реализация каждого поведения от типа должна иметь доступ к состоянию конкретного вокселя.

- Воксели хранятся в массиве, поэтому структура вокселя должна иметь константный размер (ну и да, мы не хотим дёргать аллокатор отдельно на каждый из миллионов вокселей). Так что звучит разумно ограничить размер состояния некоей небольшой величиной, а сколько реально байт в нём будут использоваться пусть решает конкретный тип.

- У небольшого количества вокселей может быть большое состояние, которое не влезет в фиксированный размер. В этом случае в состоянии вокселя помещается указатель на динамически выделяемую структуру и нужно в какой-то момент (либо при уничтожении мира, либо при смене типа этого вокселя) освободить память.

На Си это могло бы выглядеть как-то так:

typedef struct voxel_type voxel_type;

struct voxel {
    voxel_type *type;
    char data[16]; // Мы уверены, что на целевой платформе сюда влезет хотя бы один указатель
};

struct voxel_type {
    void (*init)(voxel_type *type, voxel *v);
    void (*destroy)(voxel_type *type, voxel *v);
    void (*to_string)(voxel_type *type, voxel *v, char *buffer, size_t buffer_size);
    texture_t *(*get_texture)(voxel_type *type, voxel *v);
    void (*build_vertex_data)(voxel_type *type, voxel *v, char *buffer, size_t buffer_size);
    void (*on_click)(voxel_type *type, voxel *v);
    ...
};

void set_voxel_type(voxel *v, voxel_type *t) {
    v->type->destroy(v->type, v); // Подразумевается, что изначально все воксели инициализированы каким-нибудь "пустым" типом при создании их массива
    v->type = t;
    t->init(t, v);
}

void voxel_to_string(voxel *v, char *buffer, size_t buffer_size) {
    v->type->to_string(v->type, v, buffer, size);
}

Соответственно, каждая функция из структуры voxel_type внутри себя кастует data в конкретную для данного типа вокселя структуру состояния (которая обязана быть не больше 16 байт размером, однако может содержать указатель на что-то большее), при этом init производит начальную инициализацию и не должен делать никаких допущений о том, что лежало в data до него (там может быть неинициализированная память или данные от предыдущего типа вокселя), а destroy может либо ничего не делать, либо освобождать память (если мы клали в data указатель).

Это похоже на класс с vtable из C++ (который создаётся через placement new на буфере), однако является более гибким механизмом: своё состояние имеет не только сам воксель, но и его тип (то есть можно в рантайме инстанцировать несколько типов вокселей с одинаковым поведением, но разными параметрами типа, например, текстурой, при этом каждый воксель не будет нести в себе эти параметры, а только указатель на «шаблон»).

Как это можно уложить на синтаксис и парадигму Rust? Разумеется, жалательно поменьше unsafe и побольше compile-time гарантий (например, было бы неплохо, если бы реализации типов вокселей сразу получали скастованное в нужный тип состояние, возможно, с помощью какой-нибудь магии на макросах).

Первая мысль - тип вокселя должен быть трейтом (соответственно, в нём оказываются все нужные методы, но без реализации), а конкретный воксель имеет в себе поле вроде kind: &'static dyn VoxelType. Преобразования типов можно сделать на макросах (чтобы был макрос для описания типа вокселя, который под капотом перенаправляет вызовы методов, принимающих &Voxel, в методы, принимающие конкретный тип состояния, а также берёт на себя реализацию методов создания и удаления состояния). Однако встаёт вопрос собственно инициализации и деинициализации состояния. Rust гораздо стороже относится к этому вопросу.

 , ,

KivApple
()

Интернет-зависимость

Форум — Talks

Все постят тупняк, а я чем хуже.

Нет, это не реквест забана, потому что ЛОР это один из десятка способов убивания времени (телеграм чаты, социальные сети, новостные сайты, хабр и т. д.) и это ничего не изменило бы.

Устроить цифровой детокс не позволяет специфика трудовой деятельности (без компьютера мне будет нечего кушать на перспективе).

От бессмысленного времяпровождения страдает работа (делаю всё в последний момент => дополнительный стресс), сфера отношений с друзьями (опаздываю на встречи, потому что «ещё одна статья», «ещё одно сообщение» и т. д.). Персональное «окно овертона» медленно ползёт, куда мне совсем не хочется.

Деятельность типа там прогулок на природе, спорта вроде как приносит удовольствие, но всегда трудно решиться начать, а при регулярном повторении ненапряжной деятельности (типа тех же прогулок), в них постепенно прокрадывается интернет (почему бы не фотографировать всё хоть немного необычное и не постить во все телеграм-чаты, где я состою?).

На мой взгляд, процесс идёт уже много лет, при этом самое интересное, что моё объективное качество жизни в течении этих нескольких лет растёт:

- Безработный студент-девственник, ковыряющий линуксы всё свободное время и считающий, что все окружающие люди недостойны его внимания и мечтающий править миром (разумеется, ничего для этого не делаяющий, кроме фантазирования), старательно и демонстративно избегающий общества.

- Оказывается, с людьми иногда можно приятно общаться, а «тяночки» иногда даже дают и иногда даже сами надеятся на что-то (правда, не долго, но это технические детали, главное галочку можно поставить, со времён школы не страдал от неудач в личной жизни дольше нескольких дней)

- Оказывается, за твоё хобби могут платить в несколько раз больше средней зарплаты по стране, а реально напрягаться нужно от силы час в день, если считать в среднем за год. Внезапно, 90% проблем в жизни перешло из категории «проблем» в категорию «расходов».

- Всю жизнь пугался от новостей вида «кого-то посадили за одиночный пикет» и негодавал от того, что в твоём городе убрали троллейбусы, как до этого трамваи? Вот тебе другая страна, где таких ассоциаций нет и в твоём субъективном восприятии всё выглядит максимально правильно (примерно как в советских книжках рисовали коммунизм будущего). И даже оказывается при общении на английском не чувствуешь принципиальной разницы с русским (только на некоторые темы слова сложно подбирать), да и местный язык постепенно учится и вроде каждая мелочь «я смог оформить заказ в ресторане не сказав ни слова на английском» отправляет в эйфорию. Статьи обещают падение «розовых очков» за полгода после эмиграции, а у тебя с каждым месяцем уже год (а если не считать двухлетнюю паузу, то полтора года) всё крепнет желание просто лечь где-нибудь посередине улицы, раскинуть руки в стороны и проникаться любовью к городу и стране, где ты теперь живёшь (нет, ну, разумеется, я веду себя адекватно и так не делаю). И это даже несмотря на весь негатив вроде ковид-ограничений.

- Вот тебе конкретные цели в жизни вместо недостижимого манямирка «править миром» - получить паспорт новой страны (программа-миниум с конкретными шагами на несколько лет, которые я на 146% могу осилить), опционально заработать неприлично много денег (должны же быть не простые мечты, не так ли? в любом случае это уже не вопрос выживания). Перестал ходить кругами по квартире и мечтать как «космические корабли будут бороздить большой театр» (под, разумеется, моим руководством), вот вообще. Все мечты и фантазии стали конкретными и достижимыми.

- Всю жизнь сторонился общества? Уже знаешь 5-10 местных, причём половина прожили здесь всю жизнь (общаться только с выходцами из СНГ это не спортивно). При этом ты сам был инициатором знакомства в большинстве случаев и в общении с тобой часто заинтересованы. Да, кто-то мог бы лучше, но успешность социальной интеграции явно не хуже среднего. Особенно на фоне отзывов «все иностранцы холодные и закрытые».

- Пара бонусов. Сам собой нашёлся вариант почти гарантированно обеспечить себе следующий три года (как раз до момента появления права на подачу прошения на гражданства - 4 года с учётом местного образования) - аспирантура (при этом напрягаться опять же придётся в среднем меньше часа в день в среднем за год). Думал, что завалил вариант с карьерой программиста? Так нет же ж, говорят, что хорошо, что ты резидент ЕС и предлагают с осени переоформиться на европейские проекты и зарплаты (в то время когда тебя полностью устраивало даже 1600 евро в месяц за аспирантуру). То есть я умудряюсь иметь две полностью независимые успешные карьерные ветки (наука + айти, независимые буквально - у меня два резюме двух «разных людей», хотя, конечно, информация перетекает туда-сюда, но ключевые моменты разные).

То есть за последние годы я перестал уходить от реальности в полностью придуманные миры, объективно обустроил что-то в своей жизни (ну местами не до конца - но покажите мне человека, у кого в жизни всё на 100% достигнуто и закончено?), но при этом всё больше ухожу в мир виртуальный - чаты, форумы и соцсети. При этом ставля под угрозу свои объективные достижения (но до сих пор везёт) и не находя удовольствия и удовлетворения от убивания времени. Возникает парадоксальная ситуация, что я избегаю действий, дающие сильные эмоции и эйфорию (и при этом без объективных причин) в пользу действий не дающих ничего ни объективно, ни субъективно. Ну либо всё, что я делаю IRL, я делаю, чтобы потом об этом похвастаться в Интернете (надо становиться блоггером?). Так или иначе, всё больше дней, когда я ничего не могу себя заставить делать, кроме скроллинга лент разных сайтов и соцсетей. А надо. Хотя бы немного. А даже дома прибраться не могу себя заставить.

Короче, либо надо симптоматически бороться с интернет-зависимостью, либо я не знаю. Можете ставить мне диагнозы, устраивать срачи по политике и т. д. Я не особо надеюсь, что получу какие-то дельные советы, но некоторые персонажи здесь постят гораздо больше тупняка, а у меня материал целых нескольких лет жизни и 1103 score. И я даже ни в коем случае не считаю мир вокруг несправедливым, мои проблемы полностью субъективны.

 ,

KivApple
()

Проблема с DNS

Форум — Admin

Имеется Wi-Fi роутер, на котором установлен OpenWrt. Wi-Fi роутер подключен кабелем к немного необычной сети, которая требует авторизацию - до успешной авторизации все запросы перенаправляются на страницу авторизации. Судя по всему, на стороне аплинка имеет место быть что-то вроде балансировки - иногда DHCP возвращает один gateway и DNS, иногда другой (всего два варианта). Соответственно, один вариант отлично работает, а второй вариант - нет. То есть перенаправление на страницу авторизации случается, но Google Chrome не может её открыть выдавая не особо информативую ошибку DNS_PROBE_STARTED (звучит как знаменитое «Ошибка: операция выполнена успешно»).

Если открыть консоль, то можно получить вот что:

$ dig pfrf04.in.crous-toulouse.fr

; <<>> DiG 9.16.1-Ubuntu <<>> pfrf04.in.crous-toulouse.fr
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 18391
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1432
;; QUESTION SECTION:
;pfrf04.in.crous-toulouse.fr.   IN      A

;; Query time: 2 msec
;; SERVER: 192.168.1.1#53(192.168.1.1)
;; WHEN: Tue Jul 06 03:27:25 DST 2021
;; MSG SIZE  rcvd: 56

$ dig @172.17.208.10 pfrf04.in.crous-toulouse.fr

; <<>> DiG 9.16.1-Ubuntu <<>> @172.17.208.10 pfrf04.in.crous-toulouse.fr
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 43806
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1432
;; QUESTION SECTION:
;pfrf04.in.crous-toulouse.fr.   IN      A

;; ANSWER SECTION:
pfrf04.in.crous-toulouse.fr. 3600 IN    A       172.17.208.10

;; Query time: 0 msec
;; SERVER: 172.17.208.10#53(172.17.208.10)
;; WHEN: Tue Jul 06 03:28:11 DST 2021
;; MSG SIZE  rcvd: 72

192.168.1.1 - Локальный адрес роутера (соответственно, для конечного устройства является gateway и DNS)

172.17.208.10 - gateway и DNS, полученный роутером по DHCP от аплинка

pfrf04.in.crous-toulouse.fr - страница авторизации, которая не работает (есть ещё pfrf01.in.crous-toulouse.fr - на неё редирект происходит, когда DHCP выдаёт другой gateway и DNS - 172.17.160.2 - она отлично работает в этом случае)

То есть можно заметить, что DNS аплинка и dnsmasq роутера выдают разные результаты и только ответ DNS аплинка содержит IP-адрес.

В текущий момент как костыль я просто много раз переподключаюсь к WAN, пока балансир не кинет на хороший шлюз. Но это не очень удобно, поэтому возникает вопрос как уравнять ответы dnsmasq и DNS аплинка (совсем отключать DNS-сервер на роутере я не хочу, так как когда истечёт аренда IP роутера его может кинуть на другой шлюз, а клиенты не узнают об этом, потому что их аренда IP в самом роутере истекает совсем в другое время, и продолжат долбиться в старый DNS, так что какое-то проксирование всё равно нужно).

 ,

KivApple
()

Rust и наследование

Форум — Development

Допустим, у нас есть структура игрового движка:

struct GameEngine {
    ... тут всякие кроссплатформенные поля вроде id OpenGL программ и т. д. ...
}

impl GameEngine {
    fn new() -> GameEngine {
        GameEngine {
            ...
        }
    }

    fn render(&mut self) {
        ...
    }

    ... какие-то ещё методы для обработки всяких событий ...
}

Дальше, мы хотим реализовать дочерний класс, который бы реализовывал движок под конкретную платформу. В Rust принято использовать композицию вместо наследования, окей:

struct SDLGameEngine {
    GameEngine engine;
    ... Всякие платформозависимые переменные ...
    window: sdl2::video::Window
}

impl SDLGameEngine {
    fn new() -> SDLGameEngine {
        SDLGameEngine {
            engine: GameEngine::new(),
            ...
        }
    }

    fn main_loop(&mut self) {
        loop {
            self.handle_events();
            self.engine.render();
        }
    }

    ...
}

Всё хорошо до тех пор, пока SDLGameEngine полностью рулит GameEngine - вызывает всякие разные методы в ответ на всякие разные события (рендер, нажатие клавиш, движения мыши), а GameEngine меняет только своё внутреннее состояние и дёргает вызовы OpenGL.

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

Окей, делаем трейт:

trait GameEnginePlatform {
    fn set_title(&mut self, title: &str);
}

А теперь у нас есть три варианта:

1. Реализуем трейт для SDLGameEngine (ведь в нём лежит реальное окно). Но не можем передать его в конструктор GameEngine, потому что в момент создания GameEngine SDLGameEngine ещё не существует. В свою очередь SDLGameEngine не может быть создан без экземпляра GameEngine. Начинать использовать указатели как-то глупо с учётом того, что структура очень простая (кто кем владеет) и у нас нет проблем с управлением памятью.

2. Реализуем трейт для SDLGameEngine (ведь в нём лежит реальное окно). Не будем передавать его в конструктор, добавим аргумент к функции render:

fn render(&mut self, platform: &mut dyn GameEnginePlatform) {
    ...
    platform.set_title(...);
}

...

fn main_loop(&mut self) {
    loop {
        self.handle_events();
        self.engine.render(self);
    }
}

Получаем ошибку заимствования:

   |         self.engine.render(self);
   |         ^^^^^^^^^^^^^^^^^^^----^
   |         |                  |
   |         |                  first mutable borrow occurs here
   |         second mutable borrow occurs here
   |         first borrow later captured here by trait object

В целом логично.

3. Дробим SDLGameEngine на две части. Одна основная, а другая управляет окном и реализует трейт для смены заголовка. Передаём её в конструктор GameEngine. И... Лишаемся возможности управлять окном из SDLGameEngine. А нам это хочется, ведь часть операций над окном платформозависимая.

4. Дробим SDLGameEngine на две части. Одна основная, а другая управляет окном и реализует трейт для смены заголовка. Храним её внутри SDLGameEngine, передаём ссылку в метод render. В свободное от вызова этого метода время можем управлять окном сами.

Получается, что в Rust единственный способ связи родителя класса с потомком (в терминах ООП) это вариант 4. Или есть альтернативы? Мне не очень нравится, что GameEngine теперь может обратиться к SDLGameEngine исключительно в специальных методах, которые принимают специальные параметры, а SDLGameEngine пришлось распилить на две части. В данном примере всё очень примитивно, однако в более сложной программе, мне кажется, это может стать проблемой.

Бонусный вопрос: я правильно понимаю, что trait это фактически vtable отделённый от самого объекта и в Rust каждая структура может иметь множество vtable? Как это вообще реализовано на низком уровне?

 ,

KivApple
()

Rust и декомпозиция мутабельных методов класса

Форум — Development

Вот простой код:

struct MyStruct {
    ...
}

impl MyStruct {
    fn foo(&mut self) {
        ...
        self.bar();
        ...
    }

    fn bar(&mut self) {
        ...
    }
}

Он не собирается, потому что cannot borrow `*self` as mutable more than once at a time.

Я не хочу писать километровые функции и хочу декомпозировать метод класса на несколько подметодов, которые друг-друга вызывают, каждый из которых можно комфортно редактировать. Меня даже устроит, если эти вспомогательные методы будут private, если Rust иначе не может доказать корректность. По факту я просто хочу разбить функцию на несколько частей. При этом дробить сам класс на несколько классов я не хочу, так как оба метода работают над одними и теми же данными.

Как это делается в Rust?

 ,

KivApple
()

Включение фич зависимостей в зависимости от платформы

Форум — Development

Играюсь с Rust и захотел собирать пет-проект одновременно для и натива и под WASM. Проект использует SDL2, при сборке под натив используются фичи «bundled» и «static-link», однако они не совместимы с WASM. Пытаюсь сделать так:

[target.'cfg(not(target_os = "emscripten"))'.dependencies]
sdl2 = { version = "0.34.5", features = ["bundled", "static-link"] }

[target.'cfg(target_os = "emscripten")'.dependencies]
sdl2 = { version = "0.34.5", features = [] }

Однако, это не работает - sdl2 подключается с активированными фичами даже при сборке под emscripten (в итоге сборка падает). А надо, чтобы при сборке под emscripten фичи были отключены.

Всё начинает работать только если вообще убрать первую конфигурацию из файла Cargo.toml, но я всё же хочу их использовать при нативной сборке.

P. S.: Команда сборки под WASM:

cargo.exe build --target wasm32-unknown-emscripten ...

Как быть?

 , ,

KivApple
()

C++: Вызов метода из шаблона, если он есть

Форум — Development

Хочу, чтобы шаблонная функция делала какую-то работу и по возможности в определённый момент времени вызывала метод класса. Но если этого метода нет, то ничего страшного.

template<typename T> void f(T &t, int a, int b) {
    std::cout << a << std::endl;
    t.someMethod(a, b); // Если T не имеет someMethod, эта строчка должна быть проигнорирована, но остальные исполняться как раньше
    std::cout << b << std::endl;
}

Я знаю, что есть SFINAE, так что могу написать так:

template<typename T> void f(T &t, int a, int b) {
    std::cout << a << std::endl;
    t.someMethod(a, b);
    std::cout << b << std::endl;
}

template<typename T> void f(T &t, int a, int b) {
    std::cout << a << std::endl;
    std::cout << b << std::endl;
}

То есть две версии шаблона для двух случаев. Если у T нет someMethod, то первый шаблон будет проигнорирован. Проблема в том, что если у T есть someMethod, то получится две функции с одинаковой сигнатурой. А на практике компилятор вообще видит, что я пытаюсь объявить два раза шаблон с одинаковой сигнатурой и ругается сразу, не дожидаясь попытки использовать шаблон с классом имеющим someMethod.

Как можно сделать опциональный вызов метода класса из шаблона?

 ,

KivApple
()

Пересечение луча и треугольника

Форум — Development

Имеется трехмерный треугольник, заданный тремя вершинами V1, V2, V3, имеется луч заданный точкой начала и вектором направления (на самом деле меня интересует отрезок, а не бесконечный луч, но проверить расстояние легко). Хочу получить глобальные координаты точки пересечения (если она есть, разумеется).

В проекте подключена библиотека GLM, в ней есть функция intersectRayTriangle, которая по исходным данным находит точку пересечения и расстояние. Проблема в том, что координаты точки получаются двумерные в плоскости треугольника (при этом система координат построена из векторов V1-V2 и V1-V3 - она легко может быть даже непрямоугольная). Мне интересно, как перенесети их в систему координат мира.

 

KivApple
()

Type erasure в C++

Форум — Development

Допустим, у нас есть простой класс с виртуальным методом:

class A {
public:
    virtual ~A() = default;
    virtual void f(void *ptr) = 0;
};

Подразумевается, что разные реализации A принимают в методе f указатели на разные объекты и как-то их обрабатывают. То есть, типичная реализация метода f такая:

void B::f(void *ptr) {
    auto data = (SomeType*) ptr; // SomeType разный для разных наследников A
    //делаем что-нибудь с data
}

Допустим, я хочу избавиться от необходимости кастовать ptr в конкретный тип в каждом наследнике класса A (например, потому что методов подобных f очень много и наследников очень много).

И я делаю что-то вроде такого:

template<typename T> class ATemplate: public A {
protected:
    virtual void realF(T *t) = 0;

public:
    void f(void *ptr) final {
        realF(static_cast<T*>(ptr));
    }

};

Теперь можно наследоваться не от A, а от ATemplate, передать один раз желаемый тип в шаблон и самому руками уже не надо ничего кастовать (функций подобных f может быть много, но все работают с одним и тем же типом в рамках одной и той же реализации A).

Однако если посмотреть на результат компиляции, то теперь у нас при каждом вызове f происходит два косвенных перехода - сначала при вызове f (ведь это виртуальная функция), а затем при вызове realF (ведь это тоже виртуальная функция). А хотелось бы обойтись одним переходом. Ведь на самом деле можно было бы подложить адрес realF на место f в таблице виртуальных методов (кастование указателя это лишь синтаксический сахар и на уровне ассемблерного кода не создаёт инструкции, поскольку это лишь вопрос интерпритации указателя дальше по коду, а сам указатель не меняется).

В Java есть type erasure и поэтому можно было написать просто:

class A<T> {
    public abstract void f(T arg);
};

class B<SomeType> {
    public void f(SomeType arg) {
        // что-то делаем
    }
};

В C++ мы не можем сделать базовый класс (A) шаблонным, иначе сломается runtime полиморфизм. Чтобы и не надо было делать явный каст в наследниках, и полиморфизм работал, пришлось сделать костыль с промежуточным шаблоном-наследником, но он приводит к генерации неоптимального кода.

Какие ещё есть варианты повторить то, что можно сделать в Java?

 , ,

KivApple
()

e-mail клиент

Форум — Mobile

Разыскивается e-mail клиент для Android со следующими характеристиками:

  • Удобный

  • Соблюдающий худо-бедно гайдлайны дизайна ОС

  • Возможность добавить несколько ящиков по протоколу IMAP (при этом не только популярные вещи типа GMail, но и произвольные сервера)

  • Push-уведомления о новых сообщениях

  • Либо бесплатный, либо не дороже пары евро единоразово. Без рекламы, без подписки, без 100500 дополнительных фич, каждая за отдельную плату и т. д.

До настоящего момента использовал только GMail с официальным приложением, меня он вполне устраивал, но сейчас появилась необходимость использовать несколько почтовых ящиков на других серверах (работа, учёба) и по соображениям конфеденциальности (всякие NDA и т. д.) я не очень хотел бы использовать функцию сбора писем на GMail (а ещё у меня дикий срач на моём GMail, который я уже никогда не разгребу явно).

UPD: Взял Aquamail.

 , , ,

KivApple
()

RSS подписка на новые темы