Isolate 작업 추가

This commit is contained in:
toki 2023-03-19 09:08:43 +09:00
parent e28efbe402
commit a8a52fb86a
16 changed files with 564 additions and 7 deletions

13
.vscode/launch.json vendored Normal file
View file

@ -0,0 +1,13 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "dart_framework",
"request": "launch",
"type": "dart"
}
]
}

34
bin/main.dart Normal file
View file

@ -0,0 +1,34 @@
import 'package:dart_framework/core/isolate_manager.dart';
import 'package:dart_framework/core/log.dart';
Future<void> main() async {
log('test');
log('test2');
test();
}
void test() async {
await Future.delayed(const Duration(seconds: 1));
log('this is test1');
log('this is test2');
await Future.delayed(const Duration(seconds: 2));
log('this is test3');
log('this is test4');
log('this is test5');
await Future.delayed(const Duration(seconds: 2));
log('this is test6');
log('this is test7');
await Future.delayed(const Duration(seconds: 1));
log('this is test8');
log('this is test9');
await Future.delayed(const Duration(seconds: 3));
log('this is test10');
log('this is test11');
log('this is test12');
log('this is test13');
}

3
json_01.json Normal file
View file

@ -0,0 +1,3 @@
{
"a": "foo"
}

4
json_02.json Normal file
View file

@ -0,0 +1,4 @@
{
"a": "foo",
"b": "bar"
}

5
json_03.json Normal file
View file

@ -0,0 +1,5 @@
{
"a": "foo",
"b": "bar",
"c": "bar"
}

View file

@ -0,0 +1,253 @@
import 'dart:mirrors';
import 'dart:isolate';
import 'dart:async';
/// ********************** Test ************************* ///
void test() {
IsolateManager.create<IsolateTemplate>((isoEvent, data) async {
if (isoEvent == IsolateEvent.ready) {
int id = data as int;
IsolateManager.sendBroadcast(IsoMessege('test1'));
await Future.delayed(Duration(seconds: 2));
IsolateManager.send(id, IsoMessege('test2'));
IsolateManager.send(id, IsoMessege('test1'));
IsolateManager.exit(id);
} else if (isoEvent == IsolateEvent.exit) {
print('Isolate is exited.');
}
print('On Event from Isolate: ${isoEvent.name}');
}, (message) {
print('On Message from Isolate: ${message.type}');
});
}
class IsolateTemplate extends IsolateBase {
@override
void onReady() async {
print('[IsolateTemplate] onReady');
addEventListener('test1', onTest1Handler, executeOnce: true);
addEventListener('test2', onTest2Handler);
}
void onTest1Handler(Object? data) {
print('[IsolateTemplate] OnTest1Handler: $data');
}
void onTest2Handler(Object? data) {
print('[IsolateTemplate] OnTest2Handler: $data');
}
}
/// ********************** Define *********************** ///
enum IsolateEvent { ready, exit }
class IsoMessege {
late String type;
late Object? message;
IsoMessege(this.type, {this.message});
}
class _IsolateInnerEvent {
static const String ready = '_IsolateInnerEvent.Ready';
static const String exit = '_IsolateInnerEvent.Exit';
}
class _IsolateConstructData {
late int id;
late SendPort mainIsoSender;
_IsolateConstructData(this.id, this.mainIsoSender);
}
class IsolateManager {
IsolateManager._privateConstructor();
static final IsolateManager _instance = IsolateManager._privateConstructor();
static IsolateManager get to => _instance;
int _nonce = 0;
int get nonce {
if (_nonce == double.maxFinite.toInt()) {
_nonce = 0;
} else {
_nonce++;
}
return _nonce;
}
final Map<int, IsolateBase> _isoMap = {};
final Map<int, Map<String, Function(Object?)>> _innerEventMap = {};
static void create<T extends IsolateBase>(
Function(IsolateEvent, Object?) isolateEventListener,
Function(IsoMessege) messageListener) async {
to._create<T>(isolateEventListener, messageListener);
}
void _create<T extends IsolateBase>(
Function(IsolateEvent, Object?) isolateEventListener,
Function(IsoMessege) messageListener) async {
final isoListener = ReceivePort();
var id = nonce;
T inst = Activator.createInstance(T, {});
_isoMap[id] = inst;
_addInnerEvent(id, _IsolateInnerEvent.ready, (message) {
inst.isoListener = message as SendPort;
isolateEventListener(IsolateEvent.ready, id);
});
await Isolate.spawn(
inst.initialize, _IsolateConstructData(id, isoListener.sendPort));
await for (IsoMessege message in isoListener) {
if (message.type == _IsolateInnerEvent.exit) {
isolateEventListener(IsolateEvent.exit, null);
break;
} else if (_filterInnerEvent(id, message)) {
messageListener(message);
}
}
isoListener.close();
_isoMap.remove(id);
}
void _addInnerEvent(int id, String type, Function(Object?) listener) {
Map<String, Function(Object?)>? eventMap;
if (!_innerEventMap.containsKey(id)) {
eventMap = {};
_innerEventMap[id] = eventMap;
}
eventMap![type] = listener;
}
bool _filterInnerEvent(int id, IsoMessege message) {
var isNotExist = true;
if (_innerEventMap.containsKey(id) &&
_innerEventMap[id]!.containsKey(message.type)) {
isNotExist = false;
_innerEventMap[id]![message.type]!(message.message);
}
return isNotExist;
}
static void sendBroadcast(IsoMessege message) {
var entries = to._isoMap.entries;
for (var entry in entries) {
entry.value.insertEvent(message);
}
}
static void send(int id, IsoMessege message) {
var map = to._isoMap;
if (map.containsKey(id)) {
map[id]?.insertEvent(message);
}
}
static void exit(int id) {
var map = to._isoMap;
if (map.containsKey(id)) {
map[id]!.insertEvent(IsoMessege(_IsolateInnerEvent.exit));
}
}
}
class _IsolateEventListener {
late bool executeOnce;
late Function(Object?) listener;
_IsolateEventListener(this.listener, this.executeOnce);
}
class IsolateBase {
late int id;
late ReceivePort _mainIsoListener;
late SendPort _mainIsoSender;
late SendPort isoListener;
final Map<String, List<_IsolateEventListener>> _eventMap = {};
final List<_IsolateEventListener> _removeList = [];
// ignore: library_private_types_in_public_api
Future<void> initialize(_IsolateConstructData initData) async {
id = initData.id;
_mainIsoListener = ReceivePort();
_mainIsoSender = initData.mainIsoSender;
send(IsoMessege(_IsolateInnerEvent.ready,
message: _mainIsoListener.sendPort));
onReady();
await for (IsoMessege message in _mainIsoListener) {
if (message.type == _IsolateInnerEvent.exit) {
send(IsoMessege(_IsolateInnerEvent.exit));
break;
} else {
_onEvent(message);
}
}
Isolate.exit(_mainIsoSender);
}
void onReady() async {}
void send(IsoMessege message) {
_mainIsoSender.send(message);
}
void _onEvent(IsoMessege message) {
if (_eventMap.containsKey(message.type)) {
var list = _eventMap[message.type]!;
for (var item in list) {
item.listener(message.message);
if (item.executeOnce) {
_removeList.add(item);
}
}
if (_removeList.isNotEmpty) {
for (var item in _removeList) {
removeEventListener(message.type, item.listener);
}
_removeList.clear();
}
}
}
void addEventListener(String type, Function(Object?) listener,
{bool executeOnce = true}) {
List<_IsolateEventListener> list;
if (_eventMap.containsKey(type)) {
list = _eventMap[type]!;
} else {
list = [];
_eventMap[type] = list;
}
list.add(_IsolateEventListener(listener, executeOnce));
}
void removeEventListener(String type, Function(Object?) listener) {
if (_eventMap.containsKey(type)) {
var list = _eventMap[type]!;
for (int i = 0; i < list.length; ++i) {
var item = list[i];
if (item.listener == listener) {
list.removeAt(i);
--i;
}
}
}
}
void insertEvent(IsoMessege message) {
isoListener.send(message);
}
}
class Activator {
static createInstance(Type type, Map<Symbol, dynamic> namedArguments,
[Symbol? constructor, List? arguments]) {
constructor ??= const Symbol("");
arguments ??= const [];
var typeMirror = reflectType(type);
if (typeMirror is ClassMirror) {
return typeMirror
.newInstance(constructor, arguments, namedArguments)
.reflectee;
} else {
throw ArgumentError("Cannot create the instance of the type '$type'.");
}
}
}

View file

@ -1,4 +1,10 @@
void log(String message) {}
import 'package:dart_framework/core/isolate_manager.dart';
import 'package:dart_framework/io/log_writer_base.dart';
import 'package:dart_framework/utils/string_util.dart';
void log(String message) {
_logSend(message, LogType.verbose);
}
void logWarning(String message) {
_logSend(message, LogType.warning);
@ -21,23 +27,49 @@ class LogItem {
late LogType type;
LogItem(this.message, this.type) {
var now = DateTime.now();
date =
'${now.year.toString().substring(2, 4)}-${now.month.toString().padLeft(2, '0')}-${now.day.toString().padLeft(2, '0')} '
'${now.hour.toString().padLeft(2, '0')}:${now.minute.toString().padLeft(2, '0')}:${now.second.toString().padLeft(2, '0')}';
date = getDates();
}
@override
String toString() {
return '[$date][${type.name}] $message';
}
}
class Log {
Log._privateConstructor();
int index = 0;
StringBuffer buffer = StringBuffer();
Log._privateConstructor() {
LogWriterBase.start(_onWriterEvent);
}
static final Log _instance = Log._privateConstructor();
static Log get to => _instance;
final List<LogItem> logs = [];
Log();
Log() {
LogWriterBase.start(_onWriterEvent);
}
void _onWriterEvent(IsoMessege message) {
if (message.type == 'request') {
if (index != logs.length) {
for (int i = logs.length - index - 1; i >= 0; --i) {
buffer.writeln(logs[i].toString());
}
index = logs.length;
LogWriterBase.write(buffer.toString());
buffer.clear();
}
}
}
void append(LogItem item) {
logs.insert(0, item);
}
void clear() {
logs.clear();
index = 0;
log('Log Restarted.');
}
}

10
lib/io/file_base.dart Normal file
View file

@ -0,0 +1,10 @@
import 'package:dart_framework/io/file_io.dart'
if (dart.library.html) 'package:dart_framework/io/file_web.dart';
abstract class FileExtendBase {
static FileExtendBase get instance {
return getInstance();
}
void test();
}

13
lib/io/file_io.dart Normal file
View file

@ -0,0 +1,13 @@
import 'dart:io';
import 'package:dart_framework/io/file_base.dart';
class FileExtend extends FileExtendBase {
@override
void test() {
print('This is ${Platform.operatingSystem}');
}
}
FileExtendBase getInstance() {
return FileExtend();
}

17
lib/io/file_web.dart Normal file
View file

@ -0,0 +1,17 @@
import 'dart:html';
import 'package:dart_framework/io/file_base.dart';
class FileExtend extends FileExtendBase {
@override
void test() {
var list = querySelectorAll('div');
for (var item in list) {
print(item);
}
print('This is web!!');
}
}
FileExtendBase getInstance() {
return FileExtend();
}

View file

@ -0,0 +1,13 @@
import 'package:dart_framework/core/isolate_manager.dart';
import 'package:dart_framework/io/log_writer_io.dart'
if (dart.library.html) 'package:dart_framework/io/log_writer_web.dart';
abstract class LogWriterBase extends IsolateBase {
static void start(Function(IsoMessege) listener) {
startLog(listener);
}
static void write(String log) {
print(log);
}
}

27
lib/io/log_writer_io.dart Normal file
View file

@ -0,0 +1,27 @@
import 'dart:io';
import 'package:dart_framework/core/isolate_manager.dart';
import 'package:dart_framework/io/log_writer_base.dart';
class LogWriterIO extends LogWriterBase {
@override
void onReady() {
print('[LogWriterIO] onReady');
addEventListener('write', write);
requester();
}
void requester() async {
while (true) {
await Future.delayed(const Duration(seconds: 3));
send(IsoMessege('request'));
}
}
void write(Object? message) {
print('write');
}
}
void startLog(Function(IsoMessege) listener) {
IsolateManager.create<LogWriterIO>((event, data) {}, listener);
}

View file

@ -0,0 +1 @@
void startLog() {}

View file

@ -0,0 +1,28 @@
// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
// Read the file, spawn an isolate, send the file contents to the spawned
// isolate, and wait for the parsed JSON.
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:isolate';
const filename = 'json_01.json';
Future<void> main() async {
// Read and parse JSON data in a new isolate,
// then store the returned Dart representation.
final jsonData = await Isolate.run(() => _readAndParseJson(filename));
print('Received JSON with ${jsonData.length} keys');
}
/// Reads the contents of the file with [filename],
/// decodes the JSON, and returns the result.
Future<Map<String, dynamic>> _readAndParseJson(String filename) async {
final fileData = await File(filename).readAsString();
final jsonData = jsonDecode(fileData) as Map<String, dynamic>;
return jsonData;
}

90
lib/test/test2.dart Normal file
View file

@ -0,0 +1,90 @@
// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
// Spawn an isolate, read multiple files, send their contents to the spawned
// isolate, and wait for the parsed JSON.
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:isolate';
import 'package:async/async.dart';
const filenames = [
'json_01.json',
'json_02.json',
'json_03.json',
];
void start() async {
await for (final jsonData in _sendAndReceive(filenames)) {
print('Received JSON with ${jsonData.length} keys');
}
}
// Spawns an isolate and asynchronously sends a list of filenames for it to
// read and decode. Waits for the response containing the decoded JSON
// before sending the next.
//
// Returns a stream that emits the JSON-decoded contents of each file.
Stream<Map<String, dynamic>> _sendAndReceive(List<String> filenames) async* {
final p = ReceivePort();
await Isolate.spawn(_readAndParseJsonService, p.sendPort);
// Convert the ReceivePort into a StreamQueue to receive messages from the
// spawned isolate using a pull-based interface. Events are stored in this
// queue until they are accessed by `events.next`.
final events = StreamQueue<dynamic>(p);
// The first message from the spawned isolate is a SendPort. This port is
// used to communicate with the spawned isolate.
SendPort sendPort = await events.next;
for (var filename in filenames) {
// Send the next filename to be read and parsed
sendPort.send(filename);
// Receive the parsed JSON
Map<String, dynamic> message = await events.next;
// Add the result to the stream returned by this async* function.
yield message;
}
// Send a signal to the spawned isolate indicating that it should exit.
sendPort.send(null);
// Dispose the StreamQueue.
await events.cancel();
}
// The entrypoint that runs on the spawned isolate. Receives messages from
// the main isolate, reads the contents of the file, decodes the JSON, and
// sends the result back to the main isolate.
Future<void> _readAndParseJsonService(SendPort p) async {
print('Spawned isolate started.');
// Send a SendPort to the main isolate so that it can send JSON strings to
// this isolate.
final commandPort = ReceivePort();
p.send(commandPort.sendPort);
// Wait for messages from the main isolate.
await for (final message in commandPort) {
if (message is String) {
// Read and decode the file.
final contents = await File(message).readAsString();
// Send the result to the main isolate.
p.send(jsonDecode(contents));
} else if (message == null) {
// Exit if the main isolate sends a null message, indicating there are no
// more files to read and parse.
break;
}
}
print('Spawned isolate finished.');
Isolate.exit();
}

View file

@ -1,3 +1,17 @@
String toPascal(String raw) {
return '${raw[0].toUpperCase()}${raw.substring(1).toLowerCase()}';
}
String getDates() {
return '$getDate() $getTime()';
}
String getDate() {
var now = DateTime.now();
return '${now.year.toString().substring(2, 4)}-${now.month.toString().padLeft(2, '0')}-${now.day.toString().padLeft(2, '0')}';
}
String getTime() {
var now = DateTime.now();
return '${now.hour.toString().padLeft(2, '0')}:${now.minute.toString().padLeft(2, '0')}:${now.second.toString().padLeft(2, '0')}';
}