diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..857c250 --- /dev/null +++ b/.vscode/launch.json @@ -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" + } + ] +} \ No newline at end of file diff --git a/bin/main.dart b/bin/main.dart new file mode 100644 index 0000000..abdf3a3 --- /dev/null +++ b/bin/main.dart @@ -0,0 +1,34 @@ +import 'package:dart_framework/core/isolate_manager.dart'; +import 'package:dart_framework/core/log.dart'; + +Future 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'); +} diff --git a/json_01.json b/json_01.json new file mode 100644 index 0000000..9adc145 --- /dev/null +++ b/json_01.json @@ -0,0 +1,3 @@ +{ + "a": "foo" +} \ No newline at end of file diff --git a/json_02.json b/json_02.json new file mode 100644 index 0000000..b816dc9 --- /dev/null +++ b/json_02.json @@ -0,0 +1,4 @@ +{ + "a": "foo", + "b": "bar" +} \ No newline at end of file diff --git a/json_03.json b/json_03.json new file mode 100644 index 0000000..8109f49 --- /dev/null +++ b/json_03.json @@ -0,0 +1,5 @@ +{ + "a": "foo", + "b": "bar", + "c": "bar" +} \ No newline at end of file diff --git a/lib/core/isolate_manager.dart b/lib/core/isolate_manager.dart new file mode 100644 index 0000000..c25fc39 --- /dev/null +++ b/lib/core/isolate_manager.dart @@ -0,0 +1,253 @@ +import 'dart:mirrors'; +import 'dart:isolate'; +import 'dart:async'; + +/// ********************** Test ************************* /// +void test() { + IsolateManager.create((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 _isoMap = {}; + final Map> _innerEventMap = {}; + + static void create( + Function(IsolateEvent, Object?) isolateEventListener, + Function(IsoMessege) messageListener) async { + to._create(isolateEventListener, messageListener); + } + + void _create( + 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? 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> _eventMap = {}; + final List<_IsolateEventListener> _removeList = []; + + // ignore: library_private_types_in_public_api + Future 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 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'."); + } + } +} diff --git a/lib/core/log.dart b/lib/core/log.dart index b3cf971..b184c43 100644 --- a/lib/core/log.dart +++ b/lib/core/log.dart @@ -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 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.'); + } } diff --git a/lib/io/file_base.dart b/lib/io/file_base.dart new file mode 100644 index 0000000..56abb91 --- /dev/null +++ b/lib/io/file_base.dart @@ -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(); +} diff --git a/lib/io/file_io.dart b/lib/io/file_io.dart new file mode 100644 index 0000000..821437a --- /dev/null +++ b/lib/io/file_io.dart @@ -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(); +} diff --git a/lib/io/file_web.dart b/lib/io/file_web.dart new file mode 100644 index 0000000..104c053 --- /dev/null +++ b/lib/io/file_web.dart @@ -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(); +} diff --git a/lib/io/log_writer_base.dart b/lib/io/log_writer_base.dart new file mode 100644 index 0000000..8725778 --- /dev/null +++ b/lib/io/log_writer_base.dart @@ -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); + } +} diff --git a/lib/io/log_writer_io.dart b/lib/io/log_writer_io.dart new file mode 100644 index 0000000..6f6405d --- /dev/null +++ b/lib/io/log_writer_io.dart @@ -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((event, data) {}, listener); +} diff --git a/lib/io/log_writer_web.dart b/lib/io/log_writer_web.dart new file mode 100644 index 0000000..7d64039 --- /dev/null +++ b/lib/io/log_writer_web.dart @@ -0,0 +1 @@ +void startLog() {} diff --git a/lib/test/isolate_test.dart b/lib/test/isolate_test.dart new file mode 100644 index 0000000..4b106d5 --- /dev/null +++ b/lib/test/isolate_test.dart @@ -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 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> _readAndParseJson(String filename) async { + final fileData = await File(filename).readAsString(); + final jsonData = jsonDecode(fileData) as Map; + return jsonData; +} diff --git a/lib/test/test2.dart b/lib/test/test2.dart new file mode 100644 index 0000000..af33190 --- /dev/null +++ b/lib/test/test2.dart @@ -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> _sendAndReceive(List 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(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 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 _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(); +} diff --git a/lib/utils/string_util.dart b/lib/utils/string_util.dart index 49afeff..cbe9fb2 100644 --- a/lib/utils/string_util.dart +++ b/lib/utils/string_util.dart @@ -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')}'; +}