Compare commits
3 Commits
28ccfa72ac
...
1e4d20b6a8
| Author | SHA1 | Date | |
|---|---|---|---|
| 1e4d20b6a8 | |||
| 84f7393e78 | |||
| e79109c81a |
@@ -0,0 +1,7 @@
|
||||
> [!NOTE]
|
||||
> Викладач: Мельникова Р. В.
|
||||
>
|
||||
> Оцінка: in progress
|
||||
|
||||
> [!TIP]
|
||||
> Виконано для Linux в команді.
|
||||
@@ -0,0 +1,21 @@
|
||||
title: Керування потоками одного та декількох процесів
|
||||
subject: ОС
|
||||
doctype: ЛБ
|
||||
worknumber: 8
|
||||
mentors:
|
||||
- name: Мельнікова Р. В.,
|
||||
gender: f,
|
||||
degree: доц. каф. ПІ,
|
||||
edu_program: &EDU ПЗПІ
|
||||
university: ХНУРЕ
|
||||
authors:
|
||||
- name: Ситник Є. С.
|
||||
course: 2
|
||||
edu: *EDU
|
||||
gender: m
|
||||
group: 23-2
|
||||
- name: Малишкін. А. С.
|
||||
course: 2
|
||||
edu: *EDU
|
||||
gender: m
|
||||
group: 23-2
|
||||
|
After Width: | Height: | Size: 180 KiB |
|
After Width: | Height: | Size: 161 KiB |
|
After Width: | Height: | Size: 189 KiB |
|
After Width: | Height: | Size: 182 KiB |
@@ -0,0 +1,17 @@
|
||||
CompileFlags:
|
||||
Add: [-Wall, -Wextra, -std=c++23, -DBUILD_SHARED]
|
||||
CompilationDatabase: build/
|
||||
|
||||
Diagnostics:
|
||||
UnusedIncludes: Strict
|
||||
|
||||
InlayHints:
|
||||
Enabled: Yes
|
||||
ParameterNames: Yes
|
||||
DeducedTypes: Yes
|
||||
|
||||
Index:
|
||||
Background: Build
|
||||
|
||||
Hover:
|
||||
ShowAKA: Yes
|
||||
@@ -0,0 +1,73 @@
|
||||
{
|
||||
"files.associations": {
|
||||
"any": "cpp",
|
||||
"array": "cpp",
|
||||
"atomic": "cpp",
|
||||
"bit": "cpp",
|
||||
"cctype": "cpp",
|
||||
"charconv": "cpp",
|
||||
"chrono": "cpp",
|
||||
"clocale": "cpp",
|
||||
"cmath": "cpp",
|
||||
"compare": "cpp",
|
||||
"concepts": "cpp",
|
||||
"condition_variable": "cpp",
|
||||
"cstdarg": "cpp",
|
||||
"cstddef": "cpp",
|
||||
"cstdint": "cpp",
|
||||
"cstdio": "cpp",
|
||||
"cstdlib": "cpp",
|
||||
"cstring": "cpp",
|
||||
"ctime": "cpp",
|
||||
"cwchar": "cpp",
|
||||
"cwctype": "cpp",
|
||||
"deque": "cpp",
|
||||
"list": "cpp",
|
||||
"map": "cpp",
|
||||
"string": "cpp",
|
||||
"unordered_map": "cpp",
|
||||
"vector": "cpp",
|
||||
"exception": "cpp",
|
||||
"algorithm": "cpp",
|
||||
"functional": "cpp",
|
||||
"iterator": "cpp",
|
||||
"memory": "cpp",
|
||||
"memory_resource": "cpp",
|
||||
"numeric": "cpp",
|
||||
"optional": "cpp",
|
||||
"random": "cpp",
|
||||
"ratio": "cpp",
|
||||
"string_view": "cpp",
|
||||
"system_error": "cpp",
|
||||
"tuple": "cpp",
|
||||
"type_traits": "cpp",
|
||||
"utility": "cpp",
|
||||
"format": "cpp",
|
||||
"fstream": "cpp",
|
||||
"initializer_list": "cpp",
|
||||
"iomanip": "cpp",
|
||||
"iosfwd": "cpp",
|
||||
"iostream": "cpp",
|
||||
"istream": "cpp",
|
||||
"limits": "cpp",
|
||||
"mutex": "cpp",
|
||||
"new": "cpp",
|
||||
"numbers": "cpp",
|
||||
"ostream": "cpp",
|
||||
"print": "cpp",
|
||||
"queue": "cpp",
|
||||
"ranges": "cpp",
|
||||
"semaphore": "cpp",
|
||||
"span": "cpp",
|
||||
"sstream": "cpp",
|
||||
"stdexcept": "cpp",
|
||||
"stop_token": "cpp",
|
||||
"streambuf": "cpp",
|
||||
"text_encoding": "cpp",
|
||||
"thread": "cpp",
|
||||
"cinttypes": "cpp",
|
||||
"typeinfo": "cpp",
|
||||
"variant": "cpp",
|
||||
"shared_mutex": "cpp"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"tasks": [
|
||||
{
|
||||
"type": "cppbuild",
|
||||
"label": "C/C++: g++ build active file",
|
||||
"command": "/usr/bin/g++",
|
||||
"args": [
|
||||
"-fdiagnostics-color=always",
|
||||
"-g",
|
||||
"${file}",
|
||||
"-o",
|
||||
"${fileDirname}/${fileBasenameNoExtension}"
|
||||
],
|
||||
"options": {
|
||||
"cwd": "${fileDirname}"
|
||||
},
|
||||
"problemMatcher": [
|
||||
"$gcc"
|
||||
],
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
},
|
||||
"detail": "Task generated by Debugger."
|
||||
}
|
||||
],
|
||||
"version": "2.0.0"
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
CXX = clang++
|
||||
CXXFLAGS = -std=c++23 -Wall -Wextra -O2
|
||||
|
||||
task-1/build/app: task-1/main.cpp task-1/queue.hpp task-1/workers.hpp
|
||||
@$(CXX) $(CXXFLAGS) task-1/main.cpp -o task-1/build/app
|
||||
|
||||
task-1: task-1/build/app
|
||||
@./task-1/build/app
|
||||
|
||||
task-2/build/app: task-2/main.cpp task-2/vector.hpp task-2/workers.hpp
|
||||
@$(CXX) $(CXXFLAGS) task-2/main.cpp -o task-2/build/app
|
||||
|
||||
task-2: task-2/build/app
|
||||
@./task-2/build/app
|
||||
|
||||
task-3/build/app: task-3/main.cpp task-3/forks.hpp task-3/workers.hpp
|
||||
@$(CXX) $(CXXFLAGS) task-3/main.cpp -o task-3/build/app
|
||||
|
||||
task-3: task-3/build/app
|
||||
@./task-3/build/app
|
||||
|
||||
launcher/build/app: launcher/main.cpp launcher/queue.hpp launcher/workers.hpp
|
||||
@$(CXX) $(CXXFLAGS) launcher/main.cpp -o launcher/build/app
|
||||
|
||||
launcher/build/launcher: launcher/launcher.cpp
|
||||
@$(CXX) $(CXXFLAGS) launcher/launcher.cpp -o launcher/build/launcher
|
||||
|
||||
launcher: launcher/build/app launcher/build/launcher
|
||||
@./launcher/build/launcher
|
||||
|
||||
launcher-app: launcher/build/app
|
||||
@./launcher/build/app
|
||||
|
||||
clean:
|
||||
@find . -mindepth 2 -type d -name build -exec find {} -type f -delete \;
|
||||
@@ -0,0 +1 @@
|
||||
Hello from ./launcher/files/file1.txt
|
||||
@@ -0,0 +1 @@
|
||||
Hello from ./launcher/files/file2.txt
|
||||
@@ -0,0 +1 @@
|
||||
Hello from ./launcher/files/file3.txt
|
||||
@@ -0,0 +1,23 @@
|
||||
#include <chrono>
|
||||
#include <cstdlib>
|
||||
#include <print>
|
||||
#include <ranges>
|
||||
#include <thread>
|
||||
|
||||
int main() {
|
||||
constexpr std::int32_t runs = 3;
|
||||
constexpr auto interval = std::chrono::seconds();
|
||||
|
||||
for (std::int32_t i : std::views::iota(0, runs)) {
|
||||
std::print("\nLaunching: ./launcher/build/app (run {})\n", i + 1);
|
||||
|
||||
std::int32_t result = std::system("./launcher/build/app");
|
||||
if (result != 0) {
|
||||
std::print("Run {} failed with code {}\n", i + 1, result);
|
||||
}
|
||||
|
||||
std::this_thread::sleep_for(interval);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
#include "queue.hpp"
|
||||
#include "workers.hpp"
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <fstream>
|
||||
#include <ranges>
|
||||
#include <format>
|
||||
#include <thread>
|
||||
|
||||
int main() {
|
||||
std::string prefix = "./launcher/files";
|
||||
ipc::queue queue("/my_shared_queue");
|
||||
|
||||
std::vector<std::string> file_list = {
|
||||
prefix + "/file1.txt",
|
||||
prefix + "/file2.txt",
|
||||
prefix + "/file3.txt"
|
||||
};
|
||||
|
||||
for (auto&& name : file_list) {
|
||||
std::ofstream(name) << std::format("Hello from {}\n", name);
|
||||
}
|
||||
|
||||
std::vector<std::jthread> producers;
|
||||
for (int _ : std::views::iota(0, 2)) {
|
||||
producers.emplace_back(workers::producer, std::ref(queue), std::ref(file_list));
|
||||
}
|
||||
|
||||
std::vector<std::jthread> consumers;
|
||||
for (int _ : std::views::iota(0, 4)) {
|
||||
consumers.emplace_back(workers::consumer, std::ref(queue));
|
||||
}
|
||||
|
||||
producers.clear();
|
||||
|
||||
for (int _ : std::views::iota(0, 4)) {
|
||||
queue.push("__DONE__");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstring>
|
||||
#include <fcntl.h>
|
||||
#include <semaphore.h>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace ipc {
|
||||
|
||||
constexpr size_t MAX_PATH_LEN = 256;
|
||||
constexpr size_t QUEUE_CAPACITY = 128;
|
||||
|
||||
struct shared_queue {
|
||||
sem_t mutex;
|
||||
sem_t slots;
|
||||
sem_t items;
|
||||
size_t head;
|
||||
size_t tail;
|
||||
char data[QUEUE_CAPACITY][MAX_PATH_LEN];
|
||||
bool initialized;
|
||||
};
|
||||
|
||||
class queue {
|
||||
public:
|
||||
queue(const char *shm_name) {
|
||||
shm_fd_ = ::shm_open(shm_name, O_CREAT | O_RDWR, 0666);
|
||||
if (shm_fd_ < 0)
|
||||
throw std::runtime_error("shm_open failed");
|
||||
|
||||
if (::ftruncate(shm_fd_, sizeof(shared_queue)) < 0)
|
||||
throw std::runtime_error("ftruncate failed");
|
||||
|
||||
ptr_ = static_cast<shared_queue *>(::mmap(nullptr, sizeof(shared_queue),
|
||||
PROT_READ | PROT_WRITE,
|
||||
MAP_SHARED, shm_fd_, 0));
|
||||
if (ptr_ == MAP_FAILED)
|
||||
throw std::runtime_error("mmap failed");
|
||||
|
||||
if (!ptr_->initialized) {
|
||||
initialize();
|
||||
ptr_->initialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
~queue() {
|
||||
::munmap(ptr_, sizeof(shared_queue));
|
||||
::close(shm_fd_);
|
||||
}
|
||||
|
||||
void push(const std::string &s) {
|
||||
if (s.size() >= MAX_PATH_LEN)
|
||||
throw std::length_error("path too long");
|
||||
::sem_wait(&ptr_->slots);
|
||||
::sem_wait(&ptr_->mutex);
|
||||
|
||||
std::strncpy(ptr_->data[ptr_->tail], s.c_str(), MAX_PATH_LEN);
|
||||
ptr_->tail = (ptr_->tail + 1) % QUEUE_CAPACITY;
|
||||
|
||||
::sem_post(&ptr_->mutex);
|
||||
::sem_post(&ptr_->items);
|
||||
}
|
||||
|
||||
bool pop(std::string &out) {
|
||||
::sem_wait(&ptr_->items);
|
||||
::sem_wait(&ptr_->mutex);
|
||||
|
||||
char buf[MAX_PATH_LEN];
|
||||
std::strncpy(buf, ptr_->data[ptr_->head], MAX_PATH_LEN);
|
||||
ptr_->head = (ptr_->head + 1) % QUEUE_CAPACITY;
|
||||
|
||||
::sem_post(&ptr_->mutex);
|
||||
::sem_post(&ptr_->slots);
|
||||
|
||||
out = buf;
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
int shm_fd_;
|
||||
shared_queue *ptr_;
|
||||
|
||||
void initialize() {
|
||||
::sem_init(&ptr_->mutex, 1, 1);
|
||||
::sem_init(&ptr_->slots, 1, QUEUE_CAPACITY);
|
||||
::sem_init(&ptr_->items, 1, 0);
|
||||
ptr_->head = ptr_->tail = 0;
|
||||
}
|
||||
};
|
||||
} // namespace ipc
|
||||
@@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#include "queue.hpp"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
#include <print>
|
||||
#include <fstream>
|
||||
|
||||
namespace workers {
|
||||
void producer(ipc::queue& q, const std::vector<std::string>& files) {
|
||||
for (const auto& file : files) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
q.push(file);
|
||||
std::print("Produced: {}\n", file);
|
||||
}
|
||||
}
|
||||
|
||||
void consumer(ipc::queue& queue) {
|
||||
while (true) {
|
||||
std::string path;
|
||||
if (!queue.pop(path)) continue;
|
||||
|
||||
if (path == "__DONE__") break;
|
||||
|
||||
std::ifstream file(path);
|
||||
std::print("Consumed: {}\n", path);
|
||||
for (std::string line; std::getline(file, line);) {
|
||||
std::print("\t{}\n", line);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
Hello from ./task-1/files/file1.txt
|
||||
@@ -0,0 +1 @@
|
||||
Hello from ./task-1/files/file2.txt
|
||||
@@ -0,0 +1 @@
|
||||
Hello from ./task-1/files/file3.txt
|
||||
@@ -0,0 +1,36 @@
|
||||
#include "queue.hpp"
|
||||
#include "workers.hpp"
|
||||
|
||||
#include <format>
|
||||
#include <fstream>
|
||||
#include <ranges>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
int main() {
|
||||
std::string prefix = "./task-1/files";
|
||||
|
||||
th_safe::queue<std::string> queue;
|
||||
|
||||
std::vector<std::string> file_list = {
|
||||
prefix + "/file1.txt", prefix + "/file2.txt", prefix + "/file3.txt"};
|
||||
|
||||
for (auto &&name : file_list) {
|
||||
std::ofstream(name) << std::format("Hello from {}\n", name);
|
||||
}
|
||||
|
||||
std::vector<std::jthread> producers;
|
||||
for (std::int32_t i : std::views::iota(0, 2)) {
|
||||
producers.emplace_back(workers::producer, std::ref(queue),
|
||||
std::ref(file_list), i);
|
||||
}
|
||||
|
||||
std::vector<std::jthread> consumers;
|
||||
for (std::int32_t i : std::views::iota(0, 4)) {
|
||||
consumers.emplace_back(workers::consumer, std::ref(queue), i);
|
||||
}
|
||||
|
||||
producers.clear();
|
||||
queue.done();
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <queue>
|
||||
|
||||
namespace th_safe {
|
||||
template <typename T> class queue {
|
||||
public:
|
||||
void push(const T &value) {
|
||||
{
|
||||
std::lock_guard lock(mutex_);
|
||||
queue_.push(value);
|
||||
}
|
||||
cond_var_.notify_one();
|
||||
}
|
||||
|
||||
std::optional<T> pop() {
|
||||
std::unique_lock lock(mutex_);
|
||||
cond_var_.wait(lock, [this] { return !queue_.empty() || done_; });
|
||||
|
||||
if (queue_.empty())
|
||||
return std::nullopt;
|
||||
|
||||
T value = queue_.front();
|
||||
queue_.pop();
|
||||
return value;
|
||||
}
|
||||
|
||||
void done() {
|
||||
{
|
||||
std::lock_guard lock(mutex_);
|
||||
done_ = true;
|
||||
}
|
||||
cond_var_.notify_all();
|
||||
}
|
||||
|
||||
private:
|
||||
std::queue<T> queue_;
|
||||
std::mutex mutex_;
|
||||
std::condition_variable cond_var_;
|
||||
bool done_ = false;
|
||||
};
|
||||
} // namespace th_safe
|
||||
@@ -0,0 +1,40 @@
|
||||
#pragma once
|
||||
|
||||
#include "queue.hpp"
|
||||
#include <chrono>
|
||||
#include <fstream>
|
||||
#include <print>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
namespace workers {
|
||||
void producer(th_safe::queue<std::string> &q,
|
||||
const std::vector<std::string> &files, int id) {
|
||||
for (const auto &file : files) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
q.push(file);
|
||||
std::print("Produced ({}): {}\n", id, file);
|
||||
}
|
||||
}
|
||||
|
||||
void consumer(th_safe::queue<std::string> &q, int id) {
|
||||
while (true) {
|
||||
auto item = q.pop();
|
||||
if (!item.has_value())
|
||||
break;
|
||||
|
||||
std::ifstream file(item.value());
|
||||
if (!file) {
|
||||
std::print("Failed to open: {}\n", item.value());
|
||||
continue;
|
||||
}
|
||||
|
||||
std::print("Consumed ({}): {}\n", id, item.value());
|
||||
for (std::string line; std::getline(file, line);) {
|
||||
std::print("\t{}\n", line);
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace workers
|
||||
@@ -0,0 +1,22 @@
|
||||
#include "vector.hpp"
|
||||
#include "workers.hpp"
|
||||
|
||||
#include <atomic>
|
||||
#include <ranges>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
int main() {
|
||||
th_safe::vector<std::string> news;
|
||||
std::atomic_bool done = false;
|
||||
|
||||
std::jthread writer_thread([&] {
|
||||
workers::writer(news, 10);
|
||||
done = true;
|
||||
});
|
||||
|
||||
std::vector<std::jthread> readers;
|
||||
for (std::int32_t id : std::views::iota(1, 4)) {
|
||||
readers.emplace_back(workers::reader, std::cref(news), std::ref(done), id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <mutex>
|
||||
#include <shared_mutex>
|
||||
#include <optional>
|
||||
|
||||
namespace th_safe {
|
||||
template<typename T>
|
||||
class vector {
|
||||
public:
|
||||
void push_back(const T& value) {
|
||||
std::unique_lock lock(mutex_);
|
||||
data_.push_back(value);
|
||||
}
|
||||
|
||||
std::optional<T> back() const {
|
||||
std::shared_lock lock(mutex_);
|
||||
if (data_.empty()) return std::nullopt;
|
||||
return data_.back();
|
||||
}
|
||||
|
||||
std::size_t size() const {
|
||||
std::shared_lock lock(mutex_);
|
||||
return data_.size();
|
||||
}
|
||||
|
||||
private:
|
||||
mutable std::shared_mutex mutex_;
|
||||
std::vector<T> data_;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
#include "vector.hpp"
|
||||
#include <chrono>
|
||||
#include <print>
|
||||
#include <ranges>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
namespace workers {
|
||||
void writer(th_safe::vector<std::string> &news_vector,
|
||||
std::int32_t count = 10) {
|
||||
for (std::int32_t i : std::views::iota(0, count)) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||
|
||||
auto news = std::format("News item {}", i + 1);
|
||||
news_vector.push_back(news);
|
||||
std::print("Writer: added '{}'\n", news);
|
||||
}
|
||||
}
|
||||
|
||||
void reader(const th_safe::vector<std::string> &news_vector,
|
||||
std::atomic_bool &done, std::int32_t id) {
|
||||
std::size_t prev_size = 0;
|
||||
|
||||
while (true) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||
auto last = news_vector.back();
|
||||
|
||||
if (!last.has_value()) {
|
||||
std::print("Reader {}: no news yet\n", id);
|
||||
continue;
|
||||
}
|
||||
|
||||
std::size_t current_size = news_vector.size();
|
||||
if (current_size == prev_size && !done)
|
||||
continue;
|
||||
if (current_size == prev_size && done)
|
||||
break;
|
||||
|
||||
std::print("Reader {}: reads '{}'\n", id, *last);
|
||||
prev_size = current_size;
|
||||
}
|
||||
|
||||
std::print("Reader {}: finished.\n", id);
|
||||
}
|
||||
} // namespace workers
|
||||
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
namespace th_safe {
|
||||
class forks {
|
||||
public:
|
||||
explicit forks(std::size_t count) : mutexes_(count) {}
|
||||
|
||||
std::mutex &left(std::size_t i) { return mutexes_[i]; }
|
||||
|
||||
std::mutex &right(std::size_t i) {
|
||||
return mutexes_[(i + 1) % mutexes_.size()];
|
||||
}
|
||||
|
||||
std::size_t size() const { return mutexes_.size(); }
|
||||
|
||||
private:
|
||||
std::vector<std::mutex> mutexes_;
|
||||
};
|
||||
} // namespace th_safe
|
||||
@@ -0,0 +1,20 @@
|
||||
#include "forks.hpp"
|
||||
#include "workers.hpp"
|
||||
|
||||
#include <print>
|
||||
#include <ranges>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
int main() {
|
||||
const std::size_t num_philosophers = 4;
|
||||
const std::size_t rounds = 3;
|
||||
|
||||
th_safe::forks table(num_philosophers);
|
||||
|
||||
std::vector<std::jthread> philosophers;
|
||||
for (auto id : std::views::iota(0UL, num_philosophers)) {
|
||||
philosophers.emplace_back(workers::philosopher, id, std::ref(table),
|
||||
rounds);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
|
||||
#include "forks.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <mutex>
|
||||
#include <print>
|
||||
#include <random>
|
||||
#include <thread>
|
||||
|
||||
namespace workers {
|
||||
void philosopher(std::size_t id, th_safe::forks &table,
|
||||
std::size_t iterations = 3) {
|
||||
std::mt19937 rng(id + std::random_device{}());
|
||||
std::uniform_int_distribution<> think_time(100, 300);
|
||||
std::uniform_int_distribution<> eat_time(100, 200);
|
||||
|
||||
for (std::size_t round = 0; round < iterations; ++round) {
|
||||
std::print("Philosopher {} is thinking (round {})\n", id, round + 1);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(think_time(rng)));
|
||||
|
||||
std::mutex &left_fork = table.left(id);
|
||||
std::mutex &right_fork = table.right(id);
|
||||
|
||||
if (std::addressof(left_fork) < std::addressof(right_fork)) {
|
||||
std::scoped_lock lock(left_fork, right_fork);
|
||||
std::print("Philosopher {} is eating (round {})\n", id, round + 1);
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(eat_time(rng)));
|
||||
std::print("Philosopher {} finished eating (round {})\n", id, round + 1);
|
||||
} else {
|
||||
std::scoped_lock lock(right_fork, left_fork);
|
||||
std::print("Philosopher {} is eating (round {})\n", id, round + 1);
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(eat_time(rng)));
|
||||
std::print("Philosopher {} finished eating (round {})\n", id, round + 1);
|
||||
}
|
||||
}
|
||||
|
||||
std::print("Philosopher {} leaves the table.\n", id);
|
||||
}
|
||||
} // namespace workers
|
||||
@@ -0,0 +1,489 @@
|
||||
#import "@local/nure:0.1.0": *
|
||||
|
||||
#show: pz-lb.with(..yaml("doc.yaml"))
|
||||
|
||||
#v(-spacing)
|
||||
|
||||
== Мета роботи
|
||||
Метою даної лабораторної роботи є вивчення створення процесів та потоків при виконанні
|
||||
програм.
|
||||
|
||||
== Хід роботи
|
||||
Оскільки ми використовуємо операційну систему Linux, дану лабораторну роботу нами було виконано із використанням крос-платформних функцій стандартної бібліотеки C++, за виключенням останнього завдання, для якого було задіяно засоби POSIX API, адже стандартна бібліотека не надає засобів синхронізації процесів.
|
||||
|
||||
=== Задача №1
|
||||
Мета даного завдання -- розробити програму, в якій декілька "виробників" (потоків) будуть знаходити імена файлів і додавати їх до спільної "черги". Одночасно декілька "споживачів" (інших потоків) братимуть ці імена файлів з черги і виводитимуть вміст кожного файлу.
|
||||
|
||||
Для перевірки роботи програми використаємо 2 потоки виробника та 4 потоки споживача, які будуть обробляти 3 файли.
|
||||
|
||||
```cpp
|
||||
int main() {
|
||||
std::string prefix = "./task-1/files";
|
||||
|
||||
th_safe::queue<std::string> queue;
|
||||
|
||||
std::vector<std::string> file_list = {
|
||||
prefix + "/file1.txt",
|
||||
prefix + "/file2.txt",
|
||||
prefix + "/file3.txt"
|
||||
};
|
||||
|
||||
for (auto &&name : file_list) {
|
||||
std::ofstream(name) << std::format("Hello from {}\n", name);
|
||||
}
|
||||
|
||||
std::vector<std::jthread> producers;
|
||||
for (std::int32_t i : std::views::iota(0, 2)) {
|
||||
producers.emplace_back(
|
||||
workers::producer,
|
||||
std::ref(queue),
|
||||
std::ref(file_list),
|
||||
i
|
||||
);
|
||||
}
|
||||
|
||||
std::vector<std::jthread> consumers;
|
||||
for (std::int32_t i : std::views::iota(0, 4)) {
|
||||
consumers.emplace_back(
|
||||
workers::consumer,
|
||||
std::ref(queue),
|
||||
i
|
||||
);
|
||||
}
|
||||
|
||||
producers.clear();
|
||||
queue.done();
|
||||
}
|
||||
```
|
||||
|
||||
Для зручнішої роботи із чергою з стандартної бібліотеки в багато-потоковому контексті створимо клас обгортку, що реалізує методи взаємодії із чергою з використанням м'ютексів.
|
||||
|
||||
```cpp
|
||||
namespace th_safe {
|
||||
template <typename T> class queue {
|
||||
public:
|
||||
void push(const T &value) {
|
||||
{
|
||||
std::lock_guard lock(mutex_);
|
||||
queue_.push(value);
|
||||
}
|
||||
cond_var_.notify_one();
|
||||
}
|
||||
|
||||
std::optional<T> pop() {
|
||||
std::unique_lock lock(mutex_);
|
||||
cond_var_.wait(
|
||||
lock,
|
||||
[this] { return !queue_.empty() || done_; }
|
||||
);
|
||||
|
||||
if (queue_.empty())
|
||||
return std::nullopt;
|
||||
|
||||
T value = queue_.front();
|
||||
queue_.pop();
|
||||
return value;
|
||||
}
|
||||
|
||||
void done() {
|
||||
{
|
||||
std::lock_guard lock(mutex_);
|
||||
done_ = true;
|
||||
}
|
||||
cond_var_.notify_all();
|
||||
}
|
||||
|
||||
private:
|
||||
std::queue<T> queue_;
|
||||
std::mutex mutex_;
|
||||
std::condition_variable cond_var_;
|
||||
bool done_ = false;
|
||||
};
|
||||
} // namespace th_safe
|
||||
```
|
||||
|
||||
Виробники додають файли в чергу, а споживачі зчитують їхній вміст.
|
||||
|
||||
```cpp
|
||||
namespace workers {
|
||||
void producer(
|
||||
th_safe::queue<std::string> &q,
|
||||
const std::vector<std::string> &files,
|
||||
int id
|
||||
) {
|
||||
for (const auto &file : files) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
q.push(file);
|
||||
std::print("Produced ({}): {}\n", id, file);
|
||||
}
|
||||
}
|
||||
|
||||
void consumer(th_safe::queue<std::string> &q, int id) {
|
||||
while (true) {
|
||||
auto item = q.pop();
|
||||
if (!item.has_value())
|
||||
break;
|
||||
|
||||
std::ifstream file(item.value());
|
||||
if (!file) {
|
||||
std::print("Failed to open: {}\n", item.value());
|
||||
continue;
|
||||
}
|
||||
|
||||
std::print("Consumed ({}): {}\n", id, item.value());
|
||||
for (std::string line; std::getline(file, line);) {
|
||||
std::print("\t{}\n", line);
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace workers
|
||||
```
|
||||
|
||||
Перевіримо правильність роботи програми
|
||||
#figure(image("img/task-1-result.png", width: 70%), caption: [Результат виконання програми])
|
||||
|
||||
=== Задача №2
|
||||
Мета даного завдання -- створити програму з потоками "читачів" та "письменників". Потоки-письменники будуть додавати нові записи в кінець спільного списку новин. Паралельно потоки-читачі будуть отримувати та читати останню додану новину з цього ж списку.
|
||||
|
||||
Для перевірки роботи програми використаємо 1 потік письменник та 3 потоки читачі.
|
||||
|
||||
```cpp
|
||||
int main() {
|
||||
th_safe::vector<std::string> news;
|
||||
std::atomic_bool done = false;
|
||||
|
||||
std::jthread writer_thread([&] {
|
||||
workers::writer(news, 10);
|
||||
done = true;
|
||||
});
|
||||
|
||||
std::vector<std::jthread> readers;
|
||||
for (std::int32_t id : std::views::iota(1, 4)) {
|
||||
readers.emplace_back(workers::reader, std::cref(news), std::ref(done), id);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Для зручнішої роботи із вектором з стандартної бібліотеки в багато-потоковому контексті створимо клас обгортку, що реалізує методи взаємодії із вектором з використанням м'ютексів.
|
||||
|
||||
```cpp
|
||||
namespace th_safe {
|
||||
template<typename T>
|
||||
class vector {
|
||||
public:
|
||||
void push_back(const T& value) {
|
||||
std::unique_lock lock(mutex_);
|
||||
data_.push_back(value);
|
||||
}
|
||||
|
||||
std::optional<T> back() const {
|
||||
std::shared_lock lock(mutex_);
|
||||
if (data_.empty()) return std::nullopt;
|
||||
return data_.back();
|
||||
}
|
||||
|
||||
std::size_t size() const {
|
||||
std::shared_lock lock(mutex_);
|
||||
return data_.size();
|
||||
}
|
||||
|
||||
private:
|
||||
mutable std::shared_mutex mutex_;
|
||||
std::vector<T> data_;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
Письменник додає новини в вектор, а читачі зчитують з вектора останню додану новину.
|
||||
|
||||
```cpp
|
||||
namespace workers {
|
||||
void writer(
|
||||
th_safe::vector<std::string> &news_vector,
|
||||
std::int32_t count = 10
|
||||
) {
|
||||
for (std::int32_t i : std::views::iota(0, count)) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||
|
||||
auto news = std::format("News item {}", i + 1);
|
||||
news_vector.push_back(news);
|
||||
std::print("Writer: added '{}'\n", news);
|
||||
}
|
||||
}
|
||||
|
||||
void reader(
|
||||
const th_safe::vector<std::string> &news_vector,
|
||||
std::atomic_bool &done,
|
||||
std::int32_t id
|
||||
) {
|
||||
std::size_t prev_size = 0;
|
||||
|
||||
while (true) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||
auto last = news_vector.back();
|
||||
|
||||
if (!last.has_value()) {
|
||||
std::print("Reader {}: no news yet\n", id);
|
||||
continue;
|
||||
}
|
||||
|
||||
std::size_t current_size = news_vector.size();
|
||||
if (current_size == prev_size && !done)
|
||||
continue;
|
||||
if (current_size == prev_size && done)
|
||||
break;
|
||||
|
||||
std::print("Reader {}: reads '{}'\n", id, *last);
|
||||
prev_size = current_size;
|
||||
}
|
||||
|
||||
std::print("Reader {}: finished.\n", id);
|
||||
}
|
||||
} // namespace workers
|
||||
```
|
||||
|
||||
Перевіримо правильність роботи програми
|
||||
#figure(image("img/task-2-result.png"), caption: [Результат виконання програми])
|
||||
|
||||
=== Задача №3
|
||||
Мета даного завдання -- реалізувати програму, яка моделює відому проблему "обідаючих філософів" за допомогою потоків. Кожен потік представлятиме філософа, який проходить повний цикл дій: думає, бере по черзі дві виделки, обідає, а потім кладе виделки на місце. Цей цикл має повторюватися задану кількість разів для кожного філософа.
|
||||
|
||||
Для перевірки роботи програми використаємо 5 потоків філософів та 3 повторення.
|
||||
|
||||
```cpp
|
||||
int main() {
|
||||
const std::size_t num_philosophers = 5;
|
||||
const std::size_t rounds = 3;
|
||||
|
||||
th_safe::forks table(num_philosophers);
|
||||
|
||||
std::vector<std::jthread> philosophers;
|
||||
for (auto id : std::views::iota(0UL, num_philosophers)) {
|
||||
philosophers.emplace_back(
|
||||
workers::philosopher,
|
||||
id,
|
||||
std::ref(table),
|
||||
rounds
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Для зручної роботи із "столом" в багато-потоковому контексті створимо клас, що реалізує методи взаємодії із столом з використанням м'ютексів.
|
||||
|
||||
```cpp
|
||||
namespace th_safe {
|
||||
class forks {
|
||||
public:
|
||||
explicit forks(std::size_t count) : mutexes_(count) {}
|
||||
|
||||
std::mutex &left(std::size_t i) { return mutexes_[i]; }
|
||||
|
||||
std::mutex &right(std::size_t i) {
|
||||
return mutexes_[(i + 1) % mutexes_.size()];
|
||||
}
|
||||
|
||||
std::size_t size() const { return mutexes_.size(); }
|
||||
|
||||
private:
|
||||
std::vector<std::mutex> mutexes_;
|
||||
};
|
||||
} // namespace th_safe
|
||||
```
|
||||
|
||||
Кожен з філософів по черзі із певною затримкою виконує одну із можливих дій.
|
||||
|
||||
```cpp
|
||||
namespace workers {
|
||||
void philosopher(
|
||||
std::size_t id,
|
||||
th_safe::forks &table,
|
||||
std::size_t iterations = 3
|
||||
) {
|
||||
std::mt19937 rng(id + std::random_device{}());
|
||||
std::uniform_int_distribution<> think_time(100, 300);
|
||||
std::uniform_int_distribution<> eat_time(100, 200);
|
||||
|
||||
for (std::size_t round = 0; round < iterations; ++round) {
|
||||
std::print(
|
||||
"Philosopher {} is thinking (round {})\n",
|
||||
id,
|
||||
round + 1
|
||||
);
|
||||
std::this_thread::sleep_for(
|
||||
std::chrono::milliseconds(think_time(rng))
|
||||
);
|
||||
|
||||
std::mutex &left_fork = table.left(id);
|
||||
std::mutex &right_fork = table.right(id);
|
||||
|
||||
if (std::addressof(left_fork) < std::addressof(right_fork)) {
|
||||
std::scoped_lock lock(left_fork, right_fork);
|
||||
std::print(
|
||||
"Philosopher {} is eating (round {})\n",
|
||||
id,
|
||||
round + 1
|
||||
);
|
||||
|
||||
std::this_thread::sleep_for(
|
||||
std::chrono::milliseconds(eat_time(rng))
|
||||
);
|
||||
std::print(
|
||||
"Philosopher {} finished eating (round {})\n",
|
||||
id,
|
||||
round + 1
|
||||
);
|
||||
} else {
|
||||
std::scoped_lock lock(right_fork, left_fork);
|
||||
std::print("Philosopher {
|
||||
} is eating (round {})\n", id, round + 1);
|
||||
|
||||
std::this_thread::sleep_for(
|
||||
std::chrono::milliseconds(eat_time(rng))
|
||||
);
|
||||
std::print(
|
||||
"Philosopher {} finished eating (round {})\n",
|
||||
id,
|
||||
round + 1
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
std::print("Philosopher {} leaves the table.\n", id);
|
||||
}
|
||||
} // namespace workers
|
||||
```
|
||||
|
||||
Перевіримо правильність роботи програми
|
||||
#figure(image("img/task-3-result.png", height: 80%), caption: [Результат виконання програми])
|
||||
|
||||
=== Завдання на найвищу оцінку
|
||||
Мета даного завдання -- створити окрему програму, яка буде багаторазово запускати одну з попередніх програм. Ці запуски повинні відбуватися за певним заданим розкладом, використовуючи механізм, такий як WaitableTimer.
|
||||
|
||||
Виконаємо завдання для програми №1.
|
||||
|
||||
Перевіримо роботу із 3 окремим процесами.
|
||||
|
||||
```cpp
|
||||
int main() {
|
||||
constexpr std::int32_t runs = 3;
|
||||
constexpr auto interval = std::chrono::seconds();
|
||||
|
||||
for (std::int32_t i : std::views::iota(0, runs)) {
|
||||
std::print("\nLaunching: ./launcher/build/app (run {})\n", i + 1);
|
||||
|
||||
std::int32_t result = std::system("./launcher/build/app");
|
||||
if (result != 0) {
|
||||
std::print("Run {} failed with code {}\n", i + 1, result);
|
||||
}
|
||||
|
||||
std::this_thread::sleep_for(interval);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
Щоб кілька окремих процесів мали доступ до однієї черги використаємо функцію "mmap". Для синхронізації кількох процесів скористаємося семафорами.
|
||||
|
||||
```cpp
|
||||
namespace ipc {
|
||||
|
||||
constexpr size_t MAX_PATH_LEN = 256;
|
||||
constexpr size_t QUEUE_CAPACITY = 128;
|
||||
|
||||
struct shared_queue {
|
||||
sem_t mutex;
|
||||
sem_t slots;
|
||||
sem_t items;
|
||||
size_t head;
|
||||
size_t tail;
|
||||
char data[QUEUE_CAPACITY][MAX_PATH_LEN];
|
||||
bool initialized;
|
||||
};
|
||||
|
||||
class queue {
|
||||
public:
|
||||
queue(const char *shm_name) {
|
||||
shm_fd_ = ::shm_open(shm_name, O_CREAT | O_RDWR, 0666);
|
||||
if (shm_fd_ < 0)
|
||||
throw std::runtime_error("shm_open failed");
|
||||
|
||||
if (::ftruncate(shm_fd_, sizeof(shared_queue)) < 0)
|
||||
throw std::runtime_error("ftruncate failed");
|
||||
|
||||
ptr_ = static_cast<shared_queue *>(
|
||||
::mmap(
|
||||
nullptr,
|
||||
sizeof(shared_queue),
|
||||
PROT_READ | PROT_WRITE,
|
||||
MAP_SHARED,
|
||||
shm_fd_,
|
||||
0
|
||||
)
|
||||
);
|
||||
if (ptr_ == MAP_FAILED)
|
||||
throw std::runtime_error("mmap failed");
|
||||
|
||||
if (!ptr_->initialized) {
|
||||
initialize();
|
||||
ptr_->initialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
~queue() {
|
||||
::munmap(ptr_, sizeof(shared_queue));
|
||||
::close(shm_fd_);
|
||||
}
|
||||
|
||||
void push(const std::string &s) {
|
||||
if (s.size() >= MAX_PATH_LEN)
|
||||
throw std::length_error("path too long");
|
||||
::sem_wait(&ptr_->slots);
|
||||
::sem_wait(&ptr_->mutex);
|
||||
|
||||
std::strncpy(ptr_->data[ptr_->tail], s.c_str(), MAX_PATH_LEN);
|
||||
ptr_->tail = (ptr_->tail + 1) % QUEUE_CAPACITY;
|
||||
|
||||
::sem_post(&ptr_->mutex);
|
||||
::sem_post(&ptr_->items);
|
||||
}
|
||||
|
||||
bool pop(std::string &out) {
|
||||
::sem_wait(&ptr_->items);
|
||||
::sem_wait(&ptr_->mutex);
|
||||
|
||||
char buf[MAX_PATH_LEN];
|
||||
std::strncpy(buf, ptr_->data[ptr_->head], MAX_PATH_LEN);
|
||||
ptr_->head = (ptr_->head + 1) % QUEUE_CAPACITY;
|
||||
|
||||
::sem_post(&ptr_->mutex);
|
||||
::sem_post(&ptr_->slots);
|
||||
|
||||
out = buf;
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
int shm_fd_;
|
||||
shared_queue *ptr_;
|
||||
|
||||
void initialize() {
|
||||
::sem_init(&ptr_->mutex, 1, 1);
|
||||
::sem_init(&ptr_->slots, 1, QUEUE_CAPACITY);
|
||||
::sem_init(&ptr_->items, 1, 0);
|
||||
ptr_->head = ptr_->tail = 0;
|
||||
}
|
||||
};
|
||||
} // namespace ipc
|
||||
```
|
||||
|
||||
Перевіримо роботу зміненої програми.
|
||||
|
||||
#figure(image("img/task-4-result.png", height: 72%), caption: [Результат виконання програми])
|
||||
|
||||
== Висновки
|
||||
Під час даної лабораторної роботи ми навчились використовувати процеси та потоки при виконанні програм.
|
||||
|
||||
#show: appendices_style
|
||||
@@ -0,0 +1,6 @@
|
||||
> [!NOTE]
|
||||
> Викладач: Онищенко К. Г.; Афанасьєва І. В.
|
||||
> Оцінка: In Progress
|
||||
|
||||
> [!TIP]
|
||||
> Виконано в команді
|
||||
@@ -0,0 +1,39 @@
|
||||
title: "Проектування та розробка проекту. Метод мозкового штурму. Структурні діаграми: діаграма класів, об'єктів"
|
||||
subject: ПП
|
||||
doctype: ЛБ
|
||||
worknumber: 3
|
||||
mentors:
|
||||
- name: Онищенко К. Г.,
|
||||
gender: m,
|
||||
degree: ст. викладач кафедри ПІ,
|
||||
- name: Афанасьєва І. В.,
|
||||
gender: f,
|
||||
degree: доц. каф. ПІ,
|
||||
edu_program: &EDU ПЗПІ
|
||||
university: ХНУРЕ
|
||||
authors:
|
||||
- name: Ситник Є. С.
|
||||
course: 2
|
||||
edu: *EDU
|
||||
gender: m
|
||||
group: 23-2
|
||||
- name: Малишкін. А. С.
|
||||
course: 2
|
||||
edu: *EDU
|
||||
gender: m
|
||||
group: 23-2
|
||||
- name: Краснокутська Ю. Є.
|
||||
course: 2
|
||||
edu: *EDU
|
||||
gender: f
|
||||
group: 23-2
|
||||
- name: Семьонов. О. О.
|
||||
course: 2
|
||||
edu: *EDU
|
||||
gender: m
|
||||
group: 23-2
|
||||
- name: Петах С. І.
|
||||
course: 2
|
||||
edu: *EDU
|
||||
gender: m
|
||||
group: 23-2
|
||||
@@ -0,0 +1,163 @@
|
||||
@startuml
|
||||
skin rose
|
||||
skinparam backgroundColor #EEEBDC
|
||||
/' left to right direction '/
|
||||
|
||||
enum AttachmentType {
|
||||
+ Description
|
||||
+ DueDate
|
||||
+ File
|
||||
+ Url
|
||||
+ Text
|
||||
+ Tip
|
||||
+ Hint
|
||||
+ Warning
|
||||
+ Progress
|
||||
+ Importance
|
||||
}
|
||||
|
||||
enum RequestType {
|
||||
+ Add
|
||||
+ Update
|
||||
+ Remove
|
||||
}
|
||||
|
||||
enum AccessLevel {
|
||||
+ View
|
||||
+ AddSolutions
|
||||
+ Edit
|
||||
+ AddUsers
|
||||
+ FullAccess
|
||||
}
|
||||
|
||||
enum MembershipLevel {
|
||||
+ View
|
||||
+ AddTasks
|
||||
+ Edit
|
||||
+ AddUsers
|
||||
+ FullAccess
|
||||
}
|
||||
|
||||
enum TaskVisibility {
|
||||
+ Private
|
||||
+ Public
|
||||
+ Paid
|
||||
}
|
||||
|
||||
enum SolutionType {
|
||||
+ File
|
||||
+ Url
|
||||
+ Text
|
||||
}
|
||||
|
||||
hide AccessLevel methods
|
||||
hide MembershipLevel methods
|
||||
hide AttachmentType methods
|
||||
hide TaskVisibility methods
|
||||
hide SolutionType methods
|
||||
hide RequestType methods
|
||||
|
||||
class User {
|
||||
- id : int
|
||||
+ name : String
|
||||
+ email : String
|
||||
+ password : String
|
||||
|
||||
+ register()
|
||||
+ login()
|
||||
+ changePassword()
|
||||
}
|
||||
|
||||
class Access {
|
||||
- userId : int
|
||||
- taskId : int
|
||||
+ accessLevel : AccessLevel
|
||||
+ changeAccessLevel()
|
||||
}
|
||||
|
||||
class Task {
|
||||
- id : int
|
||||
+ attachments : List<Attachment>
|
||||
+ solutions : List<Solution>
|
||||
+ requests : List<Request>
|
||||
+ name : String
|
||||
+ visibility : TaskVisibility
|
||||
+ create()
|
||||
+ remove()
|
||||
+ fork()
|
||||
+ changeVisibility()
|
||||
+ transferOwnership()
|
||||
}
|
||||
|
||||
class Attachment {
|
||||
+ type : AttachmentType
|
||||
+ data : Blob
|
||||
+ isPrivate: boolean
|
||||
+ description : String
|
||||
}
|
||||
|
||||
class Solution {
|
||||
+ description: String
|
||||
+ type: SolutionType
|
||||
+ data: blob
|
||||
+ approve()
|
||||
+ decline()
|
||||
}
|
||||
|
||||
class TaskGroup {
|
||||
+ tasks : List<Task>
|
||||
+ name : String
|
||||
+ description : String
|
||||
+ addTask()
|
||||
+ removeTask()
|
||||
+ addUser()
|
||||
+ removeUser()
|
||||
}
|
||||
|
||||
class Membership {
|
||||
- userId : int
|
||||
+ taskGroupId : int
|
||||
+ membershipLevel: MembershipLevel
|
||||
+ changeMembershipLevel()
|
||||
}
|
||||
|
||||
class Request {
|
||||
- id : int
|
||||
- userId : int
|
||||
- attachmentId : int
|
||||
+ type : RequestType
|
||||
+ message : String
|
||||
+ isApplied
|
||||
+ isRejected
|
||||
+ apply()
|
||||
+ reject()
|
||||
+ revoke()
|
||||
}
|
||||
|
||||
hide Attachment methods
|
||||
|
||||
User -u.> Access : <<manages>>
|
||||
User -u.> Membership : <<manages>>
|
||||
User -u.> Attachment : <<owns>>
|
||||
User -u.> Request : <<supervises>>
|
||||
User -u.> Solution : <<supervises>>
|
||||
|
||||
Task -d.> Access : <<grants>>
|
||||
TaskGroup -d.> Membership : <<grants>>
|
||||
|
||||
Task -d-> Attachment : contains
|
||||
Task -d-> Request : associates
|
||||
Task -d-> Solution : associates
|
||||
|
||||
Request <-u- Attachment : associates
|
||||
|
||||
TaskGroup -u-> Task : manages
|
||||
|
||||
AttachmentType --* Attachment
|
||||
RequestType -r-* Request
|
||||
AccessLevel --* Access
|
||||
MembershipLevel --* Membership
|
||||
TaskVisibility --* Task
|
||||
SolutionType -u-* Solution
|
||||
|
||||
@enduml
|
||||
|
After Width: | Height: | Size: 50 KiB |
@@ -0,0 +1,110 @@
|
||||
@startuml
|
||||
skin rose
|
||||
skinparam backgroundColor #EEEBDC
|
||||
|
||||
object "user1: User" as user1 {
|
||||
id = 1
|
||||
name = "Alice Smith"
|
||||
email = "alice@example.com"
|
||||
}
|
||||
|
||||
object "user2: User" as user2 {
|
||||
id = 2
|
||||
name = "Bob Johnson"
|
||||
email = "bob@example.com"
|
||||
}
|
||||
|
||||
object "proj_team_alpha: TaskGroup" as taskGroup1 {
|
||||
name = "Project Alpha Team"
|
||||
description = "Tasks for Project Alpha"
|
||||
}
|
||||
|
||||
object "task_design_ui: Task" as task1 {
|
||||
id = 101
|
||||
name = "Design UI Mockups"
|
||||
visibility = Public
|
||||
}
|
||||
|
||||
object "task_refactor_code: Task" as task2 {
|
||||
id = 102
|
||||
name = "Refactor Legacy Code"
|
||||
visibility = Private
|
||||
}
|
||||
|
||||
object "attach_new_icon: Attachment" as attach1 {
|
||||
type = File
|
||||
description = "New icon suggestion"
|
||||
isPrivate = false
|
||||
}
|
||||
|
||||
object "sol_ui_mockups: Solution" as sol1 {
|
||||
description = "Figma link to mockups"
|
||||
type = Url
|
||||
}
|
||||
|
||||
object "req_add_icon: Request" as req1 {
|
||||
id = 201
|
||||
userId = 2
|
||||
attachmentId = 301
|
||||
type = Add
|
||||
message = "Proposing new icon for homepage."
|
||||
isApplied = false
|
||||
isRejected = false
|
||||
}
|
||||
|
||||
object "access_ui_task1: Access" as access1 {
|
||||
userId = 1
|
||||
taskId = 101
|
||||
accessLevel = FullAccess
|
||||
}
|
||||
|
||||
object "access_ui_task2: Access" as access2 {
|
||||
userId = 2
|
||||
taskId = 101
|
||||
accessLevel = View
|
||||
}
|
||||
|
||||
object "access_refactor_task1: Access" as access3 {
|
||||
userId = 2
|
||||
taskId = 102
|
||||
accessLevel = FullAccess
|
||||
}
|
||||
|
||||
object "memb_user1_proj: Membership" as memb1 {
|
||||
userId = 1
|
||||
taskGroupId = 1
|
||||
membershipLevel = FullAccess
|
||||
}
|
||||
|
||||
object "memb_user2_proj: Membership" as memb2 {
|
||||
userId = 2
|
||||
taskGroupId = 1
|
||||
membershipLevel = View
|
||||
}
|
||||
|
||||
taskGroup1 -r- memb1
|
||||
taskGroup1 -u---- memb2
|
||||
|
||||
memb1 -u- user1
|
||||
memb2 -u- user2
|
||||
|
||||
taskGroup1 -u-- task1
|
||||
|
||||
access1 -d- user1
|
||||
access2 -u- user2
|
||||
access3 -l- user2
|
||||
|
||||
access1 -l- task1
|
||||
access2 -d- task1
|
||||
access3 -r- task2
|
||||
|
||||
sol1 -u- task1
|
||||
sol1 -- user1
|
||||
|
||||
attach1 -- req1
|
||||
attach1 -l- task1
|
||||
|
||||
req1 -- task1
|
||||
req1 -u- user2
|
||||
|
||||
@enduml
|
||||
|
After Width: | Height: | Size: 19 KiB |
@@ -0,0 +1,225 @@
|
||||
@startuml
|
||||
skin rose
|
||||
skinparam backgroundColor #EEEBDC
|
||||
left to right direction
|
||||
|
||||
!define Cloud(name, description) usecase "description" as name [[name]] #DarkGray
|
||||
!define Kite(name, description) usecase "description" as name [[name]] #Yellow
|
||||
!define Sea(name, description) usecase "description" as name [[name]] #LightBlue
|
||||
!define Fish(name, description) usecase "description" as name [[name]] #LightGray
|
||||
!define Clam(name, description) usecase "description" as name [[name]] #Silver
|
||||
|
||||
<style>
|
||||
package {
|
||||
LineColor transparent
|
||||
BackgroundColor transparent
|
||||
Shadowing 0
|
||||
FontColor transparent
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
.visiblePackage {
|
||||
LineColor black
|
||||
Shadowing 1
|
||||
FontColor black
|
||||
}
|
||||
</style>
|
||||
hide <<visiblePackage>> stereotype
|
||||
|
||||
/' package "TaskHub" <<visiblePackage>> { '/
|
||||
package "TaskHub" {
|
||||
actor User as user
|
||||
actor Guest as guest
|
||||
actor Admin as admin
|
||||
|
||||
' Auth
|
||||
package "Auth" {
|
||||
actor "Google Auth Provider" as google
|
||||
/' Cloud(auth, "Authentication") '/
|
||||
/' guest '1' -- '' auth '/
|
||||
Kite(auth_login, "Login")
|
||||
Kite(auth_register, "Registration")
|
||||
guest "1" --- "0..1" auth_login
|
||||
guest "1" --- "0..*" auth_register
|
||||
|
||||
Sea(register_email, "Register via Email")
|
||||
auth_register ..> register_email : <<include>>
|
||||
|
||||
Fish(enter_email, "Enter Email without confirmation")
|
||||
Fish(enter_email_confirm, "Enter Email with confirmation")
|
||||
Fish(enter_password, "Enter Passwounrd")
|
||||
Fish(enter_username, "Enter Username")
|
||||
register_email ..> enter_email_confirm : <<include>>
|
||||
register_email ..> enter_password: <<include>>
|
||||
register_email ..> enter_username : <<include>>
|
||||
|
||||
Sea(login_google, "Login via Google OAuth")
|
||||
auth_login <.. login_google : <<extend>>
|
||||
|
||||
Fish(choose_account, "Choose Google account")
|
||||
login_google ..> choose_account : <<include>>
|
||||
|
||||
choose_account "0..*" -- "1" google
|
||||
|
||||
Sea(login_email, "Login via Email")
|
||||
auth_login <.. login_email: <<extend>>
|
||||
|
||||
login_email ..> enter_email : <<include>>
|
||||
login_email ..> enter_password: <<include>>
|
||||
|
||||
Sea(recover_password, "Password Recovery")
|
||||
auth_login <.. recover_password : <<extend>>
|
||||
|
||||
Fish(enter_password_new, "Enter New Password")
|
||||
recover_password ..> enter_email_confirm : <<include>>
|
||||
recover_password ..> enter_password_new : <<include>>
|
||||
}
|
||||
|
||||
package "Own Task Managment" {
|
||||
Kite(own_task_create, "Create new Task")
|
||||
user "1" --- "0..*" own_task_create
|
||||
Sea(own_task_create_name, "Enter Task Name")
|
||||
Sea(own_task_create_visibility, "Select Task Visibility")
|
||||
own_task_create ..> own_task_create_name : <<include>>
|
||||
own_task_create ..> own_task_create_visibility : <<include>>
|
||||
|
||||
Kite(own_task_delete, "Delete Task")
|
||||
user "1" --- "0..*" own_task_delete
|
||||
Sea(own_task_delete_confirm, "Enter Confirmation Code form Email")
|
||||
own_task_delete ..> own_task_delete_confirm : <<include>>
|
||||
|
||||
Kite(own_task_update, "Update Task Attachments")
|
||||
user "1" --- "0..*" own_task_update
|
||||
Sea(own_task_update_type, "Select Attachment Type")
|
||||
Sea(own_task_update_content, "Select Attachment Content")
|
||||
Sea(own_task_update_visibility, "Select Attachment visibility")
|
||||
own_task_update ..> own_task_update_type : <<include>>
|
||||
own_task_update ..> own_task_update_content : <<include>>
|
||||
own_task_update ..> own_task_update_visibility : <<include>>
|
||||
|
||||
Kite(own_task_visibility, "Change visibility of the Task")
|
||||
user "1" --- "0..*" own_task_visibility
|
||||
Sea(own_task_visibility_private, "Set Private Task Visibility")
|
||||
Sea(own_task_visibility_public, "Set Public Task Visibility")
|
||||
Sea(own_task_visibility_paid, "Set Paid Task Visibility")
|
||||
own_task_visibility <.. own_task_visibility_private : <<extend>>
|
||||
own_task_visibility <.. own_task_visibility_public : <<extend>>
|
||||
own_task_visibility <.. own_task_visibility_paid : <<extend>>
|
||||
|
||||
Kite(own_task_requests, "Manage Tasks Requests")
|
||||
user "1" --- "0..*" own_task_requests
|
||||
Sea(own_task_request_approve, "Approve Task Request")
|
||||
Sea(own_task_request_decline, "Decline Task Request")
|
||||
own_task_requests <.. own_task_request_approve : <<extend>>
|
||||
own_task_requests <.. own_task_request_decline : <<extend>>
|
||||
|
||||
Kite(own_task_access, "Manage Task Access Rights")
|
||||
user "1" --- "0..*" own_task_access
|
||||
|
||||
Sea(own_task_access_add, "Give Access for User")
|
||||
Sea(own_task_access_remove, "Remove Access of the User")
|
||||
Sea(own_task_access_update, "Update Access of the User")
|
||||
|
||||
own_task_access <.. own_task_access_add : <<extend>>
|
||||
own_task_access <.. own_task_access_remove : <<extend>>
|
||||
own_task_access <.. own_task_access_update : <<extend>>
|
||||
/' } '/
|
||||
|
||||
package "Other Tasks Interactions" {
|
||||
actor "Payment Provider" as payment
|
||||
Kite(task_fork, "Fork existing Task")
|
||||
user "1" --- "0..*" task_fork
|
||||
|
||||
Sea(task_fork_options, "Select Fork Options")
|
||||
task_fork ..> task_fork_options : <<include>>
|
||||
|
||||
Kite(other_task_request_managment, "Manage Task Requests")
|
||||
user "1" --- "0..*" other_task_request_managment
|
||||
|
||||
Sea(request_add, "Add Attachment Request")
|
||||
Sea(request_update, "Update Attachment Request")
|
||||
Sea(request_remove, "Remove Attachment Request")
|
||||
Sea(request_revoke, "Revoke existing Request")
|
||||
other_task_request_managment <.. request_add : <<extend>>
|
||||
other_task_request_managment <.. request_update: <<extend>>
|
||||
other_task_request_managment <.. request_remove : <<extend>>
|
||||
other_task_request_managment <.. request_revoke : <<extend>>
|
||||
|
||||
Kite(buy_task, "Buy Paid Task")
|
||||
user "1" --- "0..*" buy_task
|
||||
|
||||
Sea(select_payment, "Select Payment Method")
|
||||
buy_task ..> select_payment : <<include>>
|
||||
|
||||
select_payment "0..*" -- "1" payment
|
||||
}
|
||||
|
||||
package "Admin Panel" {
|
||||
Kite(manage_users, "Manage Users")
|
||||
admin "1" --- "0..*" manage_users
|
||||
|
||||
Sea(review_users, "Review User Details")
|
||||
manage_users ..> review_users : <<include>>
|
||||
|
||||
Fish(create_user, "Create New User")
|
||||
Fish(view_user_profile, "View Private Data")
|
||||
Fish(delete_user_admin, "Delete User")
|
||||
Fish(block_user_admin, "Block User")
|
||||
review_users <.. create_user : <<extend>>
|
||||
review_users <.. view_user_profile : <<extend>>
|
||||
review_users <.. delete_user_admin : <<extend>>
|
||||
review_users <.. block_user_admin : <<extend>>
|
||||
|
||||
Kite(manage_content, "Manage Content")
|
||||
admin "1" --- "0..*" manage_content
|
||||
|
||||
Sea(review_content, "Review Content Details")
|
||||
manage_content ..> review_content : <<include>>
|
||||
|
||||
Fish(approve_content, "Approve Content")
|
||||
Fish(remove_content, "Remove Content")
|
||||
Fish(warn_user, "Warn Content Author")
|
||||
review_content <.. approve_content : <<extend>>
|
||||
review_content <.. remove_content : <<extend>>
|
||||
review_content <.. warn_user : <<extend>>
|
||||
}
|
||||
|
||||
package "Task Group Management" {
|
||||
actor "Github Integration Provider" as github
|
||||
|
||||
Kite(manage_tasks_in_groups, "Manage Tasks in Groups")
|
||||
Kite(manage_user_access, "Manage User's Access")
|
||||
Kite(manage_users_tg, "Manage Users")
|
||||
Kite(tg_manage_github, "Manage Github Integration")
|
||||
user "1" --- "0..*" manage_users_tg
|
||||
user "1" --- "0..*" manage_tasks_in_groups
|
||||
user "1" --- "0..*" manage_user_access
|
||||
user "1" --- "0..*" tg_manage_github
|
||||
|
||||
Sea(add_user, "Add User")
|
||||
Sea(delete_user, "Delete User")
|
||||
manage_users_tg ..> add_user : <<include>>
|
||||
manage_users_tg ..> delete_user : <<include>>
|
||||
|
||||
Sea(set_user_access, "Change Access Level")
|
||||
manage_user_access ..> set_user_access : <<include>>
|
||||
|
||||
Sea(tg_add_task, "Add Task to Group")
|
||||
Sea(tg_create_task, "Create new Task in Group")
|
||||
Sea(tg_remove_task, "Remove Task from Group")
|
||||
manage_tasks_in_groups <.. tg_add_task : <<extend>>
|
||||
manage_tasks_in_groups <.. tg_create_task : <<extend>>
|
||||
manage_tasks_in_groups <.. tg_remove_task : <<extend>>
|
||||
|
||||
Sea(github_link_repository, "Link GitHub repository to the Group")
|
||||
Sea(github_unlink_repository, "Unlink GitHub repository from the Group")
|
||||
tg_manage_github <.. github_link_repository : <<extend>>
|
||||
tg_manage_github <.. github_unlink_repository : <<extend>>
|
||||
|
||||
github_link_repository "0..*" -- "1" github
|
||||
github_unlink_repository "0..*" -- "1" github
|
||||
}
|
||||
|
||||
}
|
||||
@enduml
|
||||
|
After Width: | Height: | Size: 98 KiB |
@@ -0,0 +1,305 @@
|
||||
#import "@local/nure:0.1.0": *
|
||||
|
||||
#import calc: max
|
||||
#import table: cell
|
||||
|
||||
#show: pz-lb.with(..yaml("doc.yaml"))
|
||||
#set text(size: 14pt)
|
||||
|
||||
#let map-column(items, min-height, column) = {
|
||||
return for i in range(0, min-height) {
|
||||
(items.at(i, default: ""),)
|
||||
}.map(e => cell(x: column)[_ #e _])
|
||||
}
|
||||
|
||||
#let crc-card(
|
||||
name: "Name",
|
||||
subclasses: (),
|
||||
superclasses: (),
|
||||
responsibilities: ("responsibilities",),
|
||||
collaborators: (),
|
||||
description: "description",
|
||||
attributes: ("attributes",),
|
||||
) = figure(
|
||||
table(
|
||||
align: left, column-gutter: (0cm, 0.5cm), columns: (3fr, 2fr, 4fr),
|
||||
|
||||
cell(x: 0, y: 0, colspan: 2)[_ #name _],
|
||||
cell(x: 0, y: 1, colspan: 2)[subclasses: _ #subclasses.join(", ") _],
|
||||
cell(x: 0, y: 2, colspan: 2)[superclasses: _ #superclasses.join(", ") _],
|
||||
|
||||
table.cell(x: 0, y: 3)[responsibilities],
|
||||
..map-column(responsibilities, 5, 0),
|
||||
|
||||
cell(x: 1, y: 3)[collaborators],
|
||||
..map-column(collaborators, 5, 1),
|
||||
|
||||
cell(x: 2, y: 0)[_ #name _],
|
||||
cell(x: 2, y: 1)[#linebreak()],
|
||||
cell(x: 2, y: 2)[Description:],
|
||||
cell(x: 2, y: 3)[_ #description _],
|
||||
|
||||
cell(x: 2, y: 4)[Attributes:],
|
||||
..map-column(attributes, 4, 2),
|
||||
),
|
||||
)
|
||||
|
||||
#let crc-cards = (
|
||||
// User
|
||||
(
|
||||
name: "User",
|
||||
description: "Represents a platform user with authentication, profile management, and subscription capabilities",
|
||||
attributes: (
|
||||
"email",
|
||||
"username",
|
||||
"password",
|
||||
"profileInfo",
|
||||
"subscriptionTier",
|
||||
),
|
||||
subclasses: (),
|
||||
superclasses: (),
|
||||
responsibilities: (
|
||||
"Authenticate user credentials",
|
||||
"Manage user profile information",
|
||||
"Track subscription status and limits",
|
||||
"Maintain user activity history",
|
||||
"Handle user preferences and settings",
|
||||
),
|
||||
collaborators: (
|
||||
"Task",
|
||||
"Solution",
|
||||
"Attachment",
|
||||
"Payment",
|
||||
"Subscription",
|
||||
),
|
||||
),
|
||||
// Task
|
||||
(
|
||||
name: "Task",
|
||||
description: "Core entity representing a task with description, attachments, access controls, and monetization options",
|
||||
attributes: (
|
||||
"title",
|
||||
"owner",
|
||||
"tags",
|
||||
"attachments",
|
||||
"accessType",
|
||||
"price",
|
||||
),
|
||||
subclasses: (),
|
||||
superclasses: (),
|
||||
responsibilities: (
|
||||
"Store task information and metadata",
|
||||
"Manage task access permissions",
|
||||
"Handle task pricing and monetization",
|
||||
"Track task completion status",
|
||||
"Maintain task versioning and history",
|
||||
"Support task cloning/forking",
|
||||
"Manage attached files and resources",
|
||||
),
|
||||
collaborators: (
|
||||
"User",
|
||||
"Solution",
|
||||
"TaskGroup",
|
||||
"Comment",
|
||||
"Payment",
|
||||
"Attachment",
|
||||
),
|
||||
),
|
||||
// Attachment
|
||||
(
|
||||
name: "Attachment",
|
||||
description: "Manages attachments for tasks and solutions with storage limits and security controls",
|
||||
attributes: (
|
||||
"creator",
|
||||
"target",
|
||||
"type",
|
||||
"data",
|
||||
),
|
||||
subclasses: (),
|
||||
superclasses: (),
|
||||
responsibilities: (
|
||||
"Enforce storage limits per subscription tier",
|
||||
"Handle file upload and download",
|
||||
"Support multiple formats",
|
||||
"Generate secure URLs",
|
||||
),
|
||||
collaborators: (
|
||||
"Task",
|
||||
"Solution",
|
||||
"User",
|
||||
"Subscription",
|
||||
),
|
||||
),
|
||||
// Solution
|
||||
(
|
||||
name: "Solution",
|
||||
description: "Represents user-submitted solutions to tasks with various formats and evaluation capabilities",
|
||||
attributes: (
|
||||
"task",
|
||||
"owner",
|
||||
"content",
|
||||
"attachments",
|
||||
"status",
|
||||
"rating",
|
||||
"isPublic",
|
||||
),
|
||||
subclasses: (),
|
||||
superclasses: (),
|
||||
responsibilities: (
|
||||
"Store solution content and attachments",
|
||||
"Track solution submission status",
|
||||
"Handle solution evaluation and rating",
|
||||
"Manage solution visibility settings",
|
||||
"Support various solution formats",
|
||||
"Maintain solution history",
|
||||
),
|
||||
collaborators: (
|
||||
"User",
|
||||
"Task",
|
||||
"Comment",
|
||||
"Rating",
|
||||
"Attachment",
|
||||
),
|
||||
),
|
||||
// TaskGroup
|
||||
(
|
||||
name: "TaskGroup",
|
||||
description: "Organizes related tasks into thematic or project-based collections with shared access settings",
|
||||
attributes: (
|
||||
"name",
|
||||
"description",
|
||||
"owner",
|
||||
"tasks",
|
||||
"accessType",
|
||||
"price",
|
||||
),
|
||||
subclasses: (),
|
||||
superclasses: (),
|
||||
responsibilities: (
|
||||
"Group related tasks together",
|
||||
"Manage group-level access permissions",
|
||||
"Handle group monetization settings",
|
||||
"Integrate with GitHub repositories",
|
||||
"Support bulk operations on tasks",
|
||||
"Maintain group metadata",
|
||||
),
|
||||
collaborators: (
|
||||
"Task",
|
||||
"User",
|
||||
"Payment",
|
||||
),
|
||||
),
|
||||
// Comment
|
||||
(
|
||||
name: "Comment",
|
||||
description: "Enables user interaction through comments on tasks with rating capabilities",
|
||||
attributes: (
|
||||
"user",
|
||||
"target",
|
||||
"content",
|
||||
),
|
||||
subclasses: (),
|
||||
superclasses: (),
|
||||
responsibilities: (
|
||||
"Store comment content and metadata",
|
||||
"Track comment timestamps",
|
||||
"Handle comment moderation",
|
||||
),
|
||||
collaborators: (
|
||||
"User",
|
||||
"Task",
|
||||
"Solution",
|
||||
),
|
||||
),
|
||||
// Payment
|
||||
(
|
||||
name: "Payment",
|
||||
description: "Handles financial transactions for task access, solutions, and subscriptions",
|
||||
attributes: (
|
||||
"user",
|
||||
"target",
|
||||
"amount",
|
||||
"currency",
|
||||
"status",
|
||||
"paymentMethod",
|
||||
),
|
||||
subclasses: (),
|
||||
superclasses: (),
|
||||
responsibilities: (
|
||||
"Process payment transactions",
|
||||
"Validate payment information",
|
||||
"Handle payment failures and retries",
|
||||
"Track payment history",
|
||||
"Integrate with payment providers",
|
||||
"Manage refunds and chargebacks",
|
||||
),
|
||||
collaborators: (
|
||||
"User",
|
||||
"Task",
|
||||
"TaskGroup",
|
||||
"Subscription",
|
||||
),
|
||||
),
|
||||
// Subscription
|
||||
(
|
||||
name: "Subscription",
|
||||
description: "Manages user subscription tiers, limits, and billing cycles",
|
||||
attributes: (
|
||||
"user",
|
||||
"tier",
|
||||
"startDate",
|
||||
"endDate",
|
||||
"status",
|
||||
"taskLimit",
|
||||
"storageLimit",
|
||||
"price",
|
||||
"billingCycle",
|
||||
),
|
||||
subclasses: (),
|
||||
superclasses: (),
|
||||
responsibilities: (
|
||||
"Manage subscription lifecycle",
|
||||
"Enforce subscription limits",
|
||||
"Handle subscription upgrades/downgrades",
|
||||
"Track usage against limits",
|
||||
"Process subscription renewals",
|
||||
),
|
||||
collaborators: (
|
||||
"User",
|
||||
"Payment",
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
#v(-spacing)
|
||||
== Мета роботи
|
||||
Отримати навички побудови статичного представлення логічної моделі
|
||||
проектованої інформаційної системи у вигляді CRC-карток, об'єктної моделі,
|
||||
діаграми класів UML.
|
||||
|
||||
== Хід роботи
|
||||
#v(-spacing)
|
||||
=== Створення Smart Use Case діаграми
|
||||
SMART Use Case Diagram це один з інструментів моделювання в UML, що візуально відображає функціональні вимоги до системи з точки зору користувача. Вона складається з акторів, варіантів використання та зв'язків між ними, демонструючи, хто і як використовує систему для досягнення певних цілей. Ця діаграма допомагає розробникам, замовникам та іншим зацікавленим сторонам краще зрозуміти обсяг та призначення системи, сприяючи ефективній комунікації та збору вимог на ранніх етапах розробки програмного забезпечення.
|
||||
#figure(image("uml/uc.svg"), caption: [SMART Use Case діаграма])
|
||||
|
||||
=== Проєктування системи методом мозкового штурму (CRC-картки)
|
||||
CRC-картки це інструмент для об'єктно-орієнтованого дизайну, що використовується переважно на початкових етапах розробки програмного забезпечення. Вони допомагають команді швидко обговорювати та визначати, які класи потрібні системі, за що відповідає кожен клас, і з якими іншими класами він взаємодіє для виконання своїх завдань.
|
||||
|
||||
Під час мозкового штурму нами було виділено картки із наступними класами:
|
||||
#crc-cards.map(e => e.name).join(", ");
|
||||
|
||||
#crc-cards.map(e => crc-card(..e)).join(line(length: 100%, stroke: 1pt + gray));
|
||||
|
||||
=== Створення діаграми класів
|
||||
Діаграма класів показує класи, їхні атрибути та операції а також зв'язки між ними. Це основна діаграма, що визначає архітектуру системи та взаємодію її компонентів.
|
||||
#figure(image("uml/class.svg"), caption: [Діаграма класів])
|
||||
|
||||
=== Створення діаграми об'єктів
|
||||
Діаграма об'єктів є миттєвим станом системи в конкретний момент часу, що демонструє конкретні екземпляри класів (об'єкти) та їхні зв'язки. На відміну від діаграми класів, яка показує загальну структуру та можливі зв'язки, діаграма об'єктів відображає реальні об'єкти з їхніми поточними значеннями атрибутів і фактичними зв'язками між ними. Вона використовується для візуалізації конкретних сценаріїв виконання та допомагає перевірити правильність діаграми класів.
|
||||
#figure(image("uml/object.svg"), caption: [Діаграма об'єктів])
|
||||
|
||||
== Висновки
|
||||
За результатами цієї лабораторної роботи ми отримали навички побудови статичного представлення логічної моделі
|
||||
проектованої інформаційної системи у вигляді CRC-карток, об'єктної моделі та діаграми класів UML.
|
||||
@@ -0,0 +1,6 @@
|
||||
> [!NOTE]
|
||||
> Викладач: Онищенко К. Г.; Афанасьєва І. В.
|
||||
> Оцінка: In Progress
|
||||
|
||||
> [!TIP]
|
||||
> Виконано в команді
|
||||
@@ -0,0 +1,39 @@
|
||||
title: "Структурні діаграми компонентів та розгортання."
|
||||
subject: ПП
|
||||
doctype: ЛБ
|
||||
worknumber: 4
|
||||
mentors:
|
||||
- name: Онищенко К. Г.,
|
||||
gender: m,
|
||||
degree: ст. викладач кафедри ПІ,
|
||||
- name: Афанасьєва І. В.,
|
||||
gender: f,
|
||||
degree: доц. каф. ПІ,
|
||||
edu_program: &EDU ПЗПІ
|
||||
university: ХНУРЕ
|
||||
authors:
|
||||
- name: Ситник Є. С.
|
||||
course: 2
|
||||
edu: *EDU
|
||||
gender: m
|
||||
group: 23-2
|
||||
- name: Малишкін. А. С.
|
||||
course: 2
|
||||
edu: *EDU
|
||||
gender: m
|
||||
group: 23-2
|
||||
- name: Краснокутська Ю. Є.
|
||||
course: 2
|
||||
edu: *EDU
|
||||
gender: f
|
||||
group: 23-2
|
||||
- name: Семьонов. О. О.
|
||||
course: 2
|
||||
edu: *EDU
|
||||
gender: m
|
||||
group: 23-2
|
||||
- name: Петах С. І.
|
||||
course: 2
|
||||
edu: *EDU
|
||||
gender: m
|
||||
group: 23-2
|
||||
@@ -0,0 +1,45 @@
|
||||
@startuml
|
||||
skin rose
|
||||
skinparam backgroundColor #EEEBDC
|
||||
left to right direction
|
||||
|
||||
package "Backend Server" as backend {
|
||||
[App.rs] --> [Router.rs]
|
||||
[App.rs] --> [Hooks.rs]
|
||||
[Router.rs] --> [Controllers.rs]
|
||||
[Router.rs] --> [DataViews.rs]
|
||||
[Controllers.rs] --> [DataModels.rs]
|
||||
[Controllers.rs] --> [Mailers.rs]
|
||||
[Controllers.rs] -r-> [DataViews.rs]
|
||||
[Controllers.rs] -l-> [APIProviders.rs]
|
||||
[DataModels.rs] -l-> [DatabaseEntities.rs]
|
||||
}
|
||||
|
||||
package "Database" as db {
|
||||
[DatabaseEntities.rs] --> [DatabaseMigrations.rs]
|
||||
}
|
||||
|
||||
package "Frontend Server" as frontend {
|
||||
[main.tsx] -u-> [routes]
|
||||
[routes] -u-> [components]
|
||||
[components] -u-> [hooks]
|
||||
[hooks] -u-> [store]
|
||||
[hooks] -u-> [api]
|
||||
[api] -r-> [types]
|
||||
[store] -l-> [types]
|
||||
}
|
||||
|
||||
|
||||
package "Mobile App" {
|
||||
[MainActivity.kt] -u-> [Fragments.kt]
|
||||
[Fragments.kt] -u-> [ViewModels.kt]
|
||||
[Fragments.kt] -r-> [Adapters.kt]
|
||||
[ViewModels.kt] -u-> [APIClient.kt]
|
||||
[APIClient.kt] -r-> [DataModels.kt]
|
||||
[DataModels.kt] -l-> [ViewModels.kt]
|
||||
}
|
||||
|
||||
[App.rs] -u- [APIClient.kt] : Rest
|
||||
[App.rs] -u- [api] : Rest
|
||||
|
||||
@enduml
|
||||
|
After Width: | Height: | Size: 32 KiB |
@@ -0,0 +1,81 @@
|
||||
@startuml
|
||||
skin rose
|
||||
skinparam backgroundColor #EEEBDC
|
||||
|
||||
node backend [
|
||||
<b>Backend Server Instance(s)</b>
|
||||
---
|
||||
App.rs
|
||||
....
|
||||
Router.rs
|
||||
....
|
||||
Hooks.rs
|
||||
....
|
||||
Controllers.rs
|
||||
....
|
||||
DataViews.rs
|
||||
....
|
||||
DataModels.rs
|
||||
....
|
||||
Mailers.rs
|
||||
....
|
||||
APIProviders.rs
|
||||
....
|
||||
APIProviders.rs
|
||||
....
|
||||
DatabaseEntities.rs
|
||||
....
|
||||
DatabaseMigrations.rs
|
||||
]
|
||||
|
||||
node db_server [
|
||||
<b>Database Server Instance(s)</b>
|
||||
---
|
||||
Actual PostgreSQL Database
|
||||
]
|
||||
|
||||
node frontend_host [
|
||||
<b>Frontend Hosting/CDN</b>
|
||||
---
|
||||
main.tsx
|
||||
....
|
||||
routes
|
||||
....
|
||||
components
|
||||
....
|
||||
hooks
|
||||
....
|
||||
store
|
||||
....
|
||||
api
|
||||
....
|
||||
types
|
||||
]
|
||||
|
||||
node workstation [
|
||||
<b>User Workstation</b>
|
||||
]
|
||||
|
||||
node mobile_devices [
|
||||
<b>User Mobile Devices</b>
|
||||
---
|
||||
MainActivity.kt
|
||||
....
|
||||
Fragments.kt
|
||||
....
|
||||
ViewModels.kt
|
||||
....
|
||||
Adapters.kt
|
||||
....
|
||||
APIClient.kt
|
||||
....
|
||||
DataModels.kt
|
||||
]
|
||||
|
||||
backend -- db_server : TCP/IP - ORM Connection
|
||||
|
||||
frontend_host -u-> workstation : HTTPS - Serves Assets
|
||||
workstation --> backend : HTTPS - REST API Calls
|
||||
mobile_devices --> backend : HTTPS - REST API Calls
|
||||
|
||||
@enduml
|
||||
|
After Width: | Height: | Size: 17 KiB |
@@ -0,0 +1,96 @@
|
||||
@startuml
|
||||
skin rose
|
||||
skinparam backgroundColor #EEEBDC
|
||||
hide empty description
|
||||
|
||||
[*] --> Registration
|
||||
|
||||
state Registration {
|
||||
Registration --> Email_Validation
|
||||
Registration --> Google_Authentication
|
||||
|
||||
Email_Validation : register()
|
||||
Email_Validation : send_confirmation_email()
|
||||
Email_Validation : validate_email()
|
||||
|
||||
Google_Authentication : login_with_google()
|
||||
Google_Authentication : validate_google_token()
|
||||
|
||||
Email_Validation --> User_Registered
|
||||
Google_Authentication --> User_Registered
|
||||
}
|
||||
|
||||
User_Registered --> Login
|
||||
|
||||
state Login {
|
||||
Login --> Credentials_Validation
|
||||
Login --> Google_Authentication_Login
|
||||
|
||||
Credentials_Validation : validate_login()
|
||||
Credentials_Validation : validate_password()
|
||||
|
||||
Google_Authentication_Login : login_via_google()
|
||||
|
||||
Credentials_Validation --> User_LoggedIn
|
||||
Google_Authentication_Login --> User_LoggedIn
|
||||
}
|
||||
|
||||
state User_LoggedIn {
|
||||
state Main_Menu {
|
||||
Main_Menu --> Task
|
||||
Main_Menu --> Task_Group
|
||||
}
|
||||
|
||||
Main_Menu : view_tasks()
|
||||
Main_Menu : view_task_groups()
|
||||
Main_Menu : search_tasks(keyword)
|
||||
Main_Menu : filter_tasks_by_access(access_type)
|
||||
|
||||
state Task {
|
||||
Task --> Create_Task
|
||||
Task --> View_Task
|
||||
Task --> Edit_Task
|
||||
Task --> Delete_Task
|
||||
}
|
||||
|
||||
Task : create(title, description)
|
||||
Task : edit(task_id, new_title, new_description)
|
||||
Task : delete(task_id)
|
||||
Task : view(task_id)
|
||||
Task : set_access(task_id, access_type)
|
||||
Task : add_attachment(task_id, file)
|
||||
Task : download_attachment(task_id, file_id)
|
||||
Task : get_attachment_list(task_id)
|
||||
Task : view_history(task_id)
|
||||
Task : remove_attachment(task_id, file_id)
|
||||
|
||||
Create_Task --> Set_Task_Access
|
||||
Edit_Task --> Set_Task_Access
|
||||
|
||||
state Set_Task_Access {
|
||||
Set_Task_Access --> Public_Access
|
||||
Set_Task_Access --> Private_Access
|
||||
Set_Task_Access --> Paid_Access
|
||||
}
|
||||
|
||||
Public_Access : access = public
|
||||
Private_Access : access = private
|
||||
Paid_Access : access = paid
|
||||
|
||||
state Task_Group {
|
||||
Task_Group --> Create_Group
|
||||
Task_Group --> View_Group
|
||||
Task_Group --> Edit_Group
|
||||
Task_Group --> Delete_Group
|
||||
}
|
||||
|
||||
Task_Group : create_group(name)
|
||||
Task_Group : add_task_to_group(task_id)
|
||||
Task_Group : remove_task_from_group(task_id)
|
||||
Task_Group : delete_group(group_id)
|
||||
Task_Group : rename_group(group_id, new_name)
|
||||
Task_Group : view_group(group_id)
|
||||
}
|
||||
|
||||
User_LoggedIn --> [*]
|
||||
@enduml
|
||||
|
After Width: | Height: | Size: 34 KiB |
@@ -0,0 +1,24 @@
|
||||
#import "@local/nure:0.1.0": *
|
||||
|
||||
#show: pz-lb.with(..yaml("doc.yaml"))
|
||||
|
||||
#v(-spacing)
|
||||
== Мета роботи
|
||||
Метою даної лабораторної роботи є вивчення моделювання структурних діаграм компонентів та розгортання
|
||||
за допомогою мови UML, та закріплення отриманих навички методом моделювання на прикладі обраної теми проекту.
|
||||
|
||||
== Хід роботи
|
||||
|
||||
Діаграма станів -- це одна з поведінкових діаграм у UML, яка описує можливі стани об'єкта та переходи між ними протягом його життєвого циклу. Вона візуалізує динамічну поведінку системи або її компонента, показуючи, як об'єкт реагує на події, змінюючи свій стан. Кожен стан представляє певний етап у житті об'єкта, а переходи між станами ініціюються подіями та можуть супроводжуватися діями.
|
||||
|
||||
Діаграма компонентів -- це одна з поведінкових діаграм у UML, яка візуалізує архітектуру системи, показуючи її компоненти, їхні інтерфейси та залежності між ними. Компонент у цьому контексті є модульною, замінною частиною системи, яка інкапсулює певну функціональність і надає її через чітко визначені інтерфейси, а також може вимагати певні інтерфейси від інших компонентів. Діаграми компонентів допомагають зрозуміти, як різні частини програмного забезпечення організовані та взаємодіють між собою, що є важливим для розгортання, підтримки та подальшого розвитку системи. Вони дозволяють моделювати фізичну структуру системи, відображаючи її на рівні програмних модулів, бібліотек, виконуваних файлів або баз даних.
|
||||
|
||||
Діаграма розгортання -- це ще один тип структурних діаграм у UML, яка показує фізичне розміщення артефактів (таких як виконувані файли, бібліотеки, документи тощо) на вузлах (фізичних апаратних пристроях або середовищах виконання). Вона візуалізує конфігурацію апаратних засобів і програмного забезпечення, що розгортається в системі. На цій діаграмі вузли представляють обчислювальні ресурси (наприклад, сервери, робочі станції, мережеві пристрої), а зв'язки між ними показують комунікаційні шляхи. Усередині вузлів розміщуються артефакти, що позначають конкретні програмні компоненти, які виконуватимуться на цих вузлах. Діаграми розгортання є незамінними для архітекторів систем та інженерів DevOps, оскільки вони допомагають планувати, документувати та візуалізувати інфраструктуру, необхідну для функціонування програмного забезпечення, а також розуміти, як різні частини системи розподілені в реальному фізичному середовищі.
|
||||
|
||||
#figure(image("uml/state.svg", width: 90%), caption: [Діаграма станів])
|
||||
#figure(image("uml/component.svg", width: 75%), caption: [Діаграма компонентів])
|
||||
#figure(image("uml/distribution.svg", width: 63%), caption: [Діаграма розгортання])
|
||||
|
||||
== Висновки
|
||||
За результатами цієї лабораторної роботи ми навчились моделювати структурні діаграм компонентів та розгортання
|
||||
за допомогою мови UML, та закріпили отримані навички методом моделювання на прикладі обраної теми проекту.
|
||||